# 9. Security

# Authentication Authorization and Admission Control

Welcome to this comprehensive lesson on Kubernetes security. In this module, we dive into the core security primitives essential for production-grade Kubernetes deployments. We'll explore how Kubernetes secures its API, manages user access, and controls inter-component communication.

**<mark>Note</mark>**

Before diving deeper, ensure your infrastructure hosts are secure by disabling root access, disabling password-based authentication, and exclusively using SSH key-based authentication.

## Securing Kubernetes Cluster Hosts

The foundation of a secure Kubernetes environment is protecting the underlying physical or virtual infrastructure. Ensure that your cluster hosts adhere to industry security best practices:

* Disable root access.
    
* Turn off password-based authentication.
    
* Enable SSH key-based authentication.
    

Remember, if the base infrastructure is compromised, the entire Kubernetes cluster is vulnerable.

## Protecting the Kube API Server

At the heart of Kubernetes lies the Kube API Server, which serves as the gateway for all operations—be it through `kubectl` or direct API calls. As such, strict control over who can access the cluster and what actions they can perform is vital.

### Authentication Methods

Authentication determines the identity of a user or a process accessing the API server. Kubernetes supports multiple authentication mechanisms, including:

* **User IDs and Passwords:** Stored in static files.
    
* **Tokens:** Issued for API access.
    
* **Certificates:** Securing communications.
    
* **External Authentication Providers:** For example, LDAP.
    
* **Service Accounts:** For machine-to-machine interactions.
    

![The image is a slide titled "Authentication" listing access methods: username/password, username/tokens, certificates, LDAP, and service accounts.](https://kodekloud.com/kk-media/image/upload/v1752871274/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication-Authorization-and-Admission-Control/frame_120.jpg align="left")

### Authorization Strategies

Once authenticated, authorization dictates what actions users are permitted to perform. Kubernetes primarily employs Role-Based Access Control (RBAC) to manage permissions. Additional strategies include:

* **Attribute-Based Access Control (ABAC)**
    
* **Node Authorization**
    
* **Webhook Modes**
    

![The image lists types of authorization: RBAC, ABAC, Node Authorization, and Webhook Mode, under the heading "Authorization: What can they do?"](https://kodekloud.com/kk-media/image/upload/v1752871276/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication-Authorization-and-Admission-Control/frame_140.jpg align="left")

## Securing Inter-Component Communications

Every component within Kubernetes communicates over a network, sometimes traversing untrusted networks. To ensure data security, Kubernetes uses TLS encryption for communications among critical components such as:

* Kube API Server
    
* etcd Cluster
    
* Kube Controller Manager
    
* Kube Scheduler
    
* Kubelet and Kube Proxy on worker nodes
    

Detailed procedures for configuring and managing TLS certificates are covered in the course content.

![The image illustrates the relationship between Kubernetes components using TLS certificates, centered around the Kube ApiServer, connecting to ETCD Cluster, Kubelet, Kube Proxy, Kube Controller Manager, and Kube Scheduler.](https://kodekloud.com/kk-media/image/upload/v1752871277/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication-Authorization-and-Admission-Control/frame_160.jpg align="left")

## Managing Pod Communications with Network Policies

By default, all pods within a Kubernetes cluster can communicate freely, which may not be desirable for every workload or application. Network policies allow administrators to define rules that restrict pod-to-pod interactions, thereby reducing potential attack vectors and isolating sensitive components.

![The image illustrates network policies using a diagram of four devices with colored circles and pentagons, indicating connections or groupings.](https://kodekloud.com/kk-media/image/upload/v1752871278/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication-Authorization-and-Admission-Control/frame_190.jpg align="left")

This high-level overview provides a solid foundation on Kubernetes security primitives. Each of these topics is explored in greater depth throughout the course, ensuring you have the knowledge to build a robust and secure Kubernetes environment.

# Authentication

Welcome to this comprehensive lesson on authentication within a Kubernetes cluster. In a typical Kubernetes environment, the cluster consists of multiple nodes—whether physical or virtual—and a range of components that work together seamlessly. The cluster is accessed by various users, including administrators managing cluster operations, developers deploying applications, end users interacting with those applications, and even third-party applications for integrations. In this lesson, we focus on securing the cluster by ensuring secure communication between internal components and by managing administrative access with robust authentication and authorization mechanisms.

![The image illustrates a network diagram showing interactions between admins, developers, end users, and bots, connected through a series of nodes.](https://kodekloud.com/kk-media/image/upload/v1752871279/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication/frame_30.jpg align="left")

Our primary focus is on authentication mechanisms that secure access for users (such as administrators and developers) and service accounts (robots or processes with cluster access). Note that while end user authentication for applications is generally managed by the application itself, Kubernetes relies on external sources for handling human user accounts.

Kubernetes itself does not manage user accounts natively. Instead, it integrates with external sources like CSV files, certificate authorities, or third-party identity services (e.g., LDAP). Although service accounts can be created and managed via the Kubernetes API, user management for non-service accounts must be handled externally. Every request—whether it comes via the kubectl tool or the API—is first authenticated by the kube-apiserver.

There are several authentication mechanisms available in Kubernetes:

* Using a static password file (a CSV file containing usernames and passwords)
    
* Using a static token file for token-based authentication
    
* Using certificates for user authentication
    
* Integrating with third-party authentication protocols such as LDAP or Kerberos
    

Below is another visual representation that categorizes accounts into "User" (Admins, Developers) and "Service Accounts" (Bots).

![The image categorizes accounts into "User" (Admins, Developers) and "Service Accounts" (Bots) with corresponding icons.](https://kodekloud.com/kk-media/image/upload/v1752871281/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication/frame_90.jpg align="left")

All authentication methods funnel through the kube-apiserver which validates each request before further processing.

![The image outlines Kubernetes authentication mechanisms, including static password files, static token files, certificates, and identity services, related to the kube-apiserver.](https://kodekloud.com/kk-media/image/upload/v1752871282/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authentication/frame_180.jpg align="left")

## Static Password File Authentication

The simplest authentication method uses a CSV file that lists users along with their passwords, usernames, and user IDs. Optionally, a fourth column can indicate group memberships. This file is made available to the kube-apiserver using the `--basic-auth-file` flag. Consider this example CSV file that stores user details:

```plaintext
password123,user1,u0001
password123,user2,u0002
password123,user3,u0003
password123,user4,u0004
password123,user5,u0005
```

To configure the kube-apiserver to use this file, update its service configuration. For clusters set up with tools like kubeadm, modify the kube-apiserver manifest (typically located at `/etc/kubernetes/manifests/kube-apiserver.yaml`) as shown below:

```plaintext
kube-apiserver.service
ExecStart=/usr/local/bin/kube-apiserver \
    --advertise-address=${INTERNAL_IP} \
    --allow-privileged=true \
    --apiserver-count=3 \
    --authorization-mode=Node,RBAC \
    --bind-address=0.0.0.0 \
    --enable-swagger-ui=true \
    --etcd-servers=https://127.0.0.1:2379 \
    --event-ttl=1h \
    --runtime-config=api/all \
    --service-cluster-ip-range=10.32.0.0/24 \
    --service-node-port-range=30000-32767 \
    --v=2 \
    --basic-auth-file=user-details.csv
```

Below is an excerpt from a modified kube-apiserver pod manifest for a kubeadm-based cluster:

```plaintext
/etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    image: k8s.gcr.io/kube-apiserver-amd64:v1.11.3
    command:
    - kube-apiserver
    - --authorization-mode=Node,RBAC
    - --advertise-address=172.17.0.107
    - --allow-privileged=true
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
```

After updating the manifest, the kube-apiserver will restart automatically (in a kubeadm environment) to apply the new authentication configuration.

## Static Token File Authentication

For token-based authentication, you can create a CSV file similar to the static password file, but with a token replacing the password. This file is provided to the kube-apiserver using the `--token-auth-file` flag. Here’s an example CSV file with tokens and group memberships:

```plaintext
KpjCvB7rcFAHYPKByTIZRb7gulcUc4B,user10,u0010,group1
rJJncHmvtxHc6M1WQddhtvNyhgTdxSC,user11,u0011,group1
mjpOFTEiFOKL9toikaRNTt59ePtczZSq,user12,u0012,group2
PG411Xhs7QjqWKmBkvgGT9glOyUqZij,user13,u0013,group2
```

When starting the kube-apiserver, include the token file as follows:

```plaintext
--token-auth-file=user-token-details.csv
```

To authenticate a request with a token, pass the token as an authorization bearer token. For example:

```plaintext
curl -v -k https://master-node-ip:6443/api/v1/pods --header "Authorization: Bearer KpjCvB7rcFAHYPKByTIZRb7gulcUc4B"
```

## Important Considerations

**<mark>Warning</mark>**

Using static password or token files is not recommended for production environments since they store sensitive information (usernames, passwords, tokens) in clear text. Consider using more secure methods such as client certificates or integrative external identity providers.

# KubeConfig

Welcome to this guide on kubeconfig files in Kubernetes. In this article, we will explore how kubeconfig files streamline authentication and context management for kubectl, enhancing your workflow by reducing repetitive command-line options.

So far, you learned how to generate a certificate for a user and how a client can use the certificate file and key to query the Kubernetes REST API. For example, assume your cluster is named "my kube playground." You can send a curl request to the Kubernetes API server with the client certificate, key, and CA certificate:

```plaintext
curl https://my-kube-playground:6443/api/v1/pods \
  --key admin.key \
  --cert admin.crt \
  --cacert ca.crt
```

The API server validates the certificate and responds with output similar to:

```plaintext
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods"
  },
  "items": []
}
```

When using kubectl, you normally pass the same connection information with the corresponding options:

```plaintext
kubectl get pods \
  --server my-kube-playground:6443 \
  --client-key admin.key \
  --client-certificate admin.crt \
  --certificate-authority ca.crt
```

This command might return:

```plaintext
No resources found.
```

Typing these options every time can become tedious. To simplify your workflow, you can move the connection details into a configuration file known as a kubeconfig file. By default, kubectl looks for a file named `config` under the `.kube` directory in your home directory. If the kubeconfig file is in its default location, you don’t have to specify connection options for each command:

```plaintext
kubectl get pods
```

**<mark>Tip</mark>**

Using a kubeconfig file saves you time by automatically applying connection settings, which means you no longer have to repeatedly supply options like `--client-key` and `--certificate-authority`.

## Kubeconfig Structure

The kubeconfig file is organized into three primary sections:

* **Clusters**: Define the various Kubernetes clusters you need access to. You might have separate clusters for development, testing, production, or different cloud providers.
    
* **Users**: Define the user accounts holding credentials (such as client certificates and keys) needed to access these clusters.
    
* **Contexts**: Link clusters and users together. A context specifies which user credentials should be used to access a particular cluster. For example, you could have a context called “admin@production,” which uses the admin user’s credentials for the production cluster.
    

These components work together to streamline connectivity and authentication in your Kubernetes environment.

![The image illustrates a KubeConfig file structure, showing clusters, contexts, and users, with examples like "Development," "Admin@Production," and "Dev User."](https://kodekloud.com/kk-media/image/upload/v1752871289/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-KubeConfig/frame_160.jpg align="left")

In our example, the server address and CA certificate information belong in the clusters section, while the admin user’s keys and certificates go in the users section. A context then binds these settings together. Below is a sample kubeconfig file in YAML format:

```plaintext
apiVersion: v1
kind: Config
clusters:
- name: my-kube-playground  # values hidden for brevity
- name: development
- name: production
- name: google
contexts:
- name: my-kube-admin@my-kube-playground
- name: dev-user@google
- name: prod-user@production
users:
- name: my-kube-admin
- name: admin
- name: dev-user
- name: prod-user
```

Note that you do not create Kubernetes objects for these configurations. Instead, kubectl reads this file to obtain the necessary connection details.

kubectl selects a context from the kubeconfig based on the `current-context` field. For example, if you set:

```plaintext
current-context: my-kube-admin@my-kube-playground
```

kubectl will default to that context. Alternatively, you can specify a different kubeconfig file from the command line using the `--kubeconfig` flag:

```plaintext
kubectl config view --kubeconfig=my-custom-config
```

### Default Kubeconfig Example

An example output of the default kubeconfig file might be:

```plaintext
apiVersion: v1
kind: Config
current-context: kubernetes-admin@kubernetes
clusters:
- cluster:
    certificate-authority-data: REDACTED
    server: https://172.17.0.5:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
users:
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
```

And when using a custom config file:

```plaintext
kubectl config view --kubeconfig=my-custom-config
apiVersion: v1
kind: Config
current-context: my-kube-admin@my-kube-playground
clusters:
- name: my-kube-playground
- name: development
- name: production
contexts:
- name: my-kube-admin@my-kube-playground
- name: prod-user@production
users:
- name: my-kube-admin
- name: prod-user
```

### Switching Contexts

To switch your current context—for example, changing from the my-kube-admin account on the playground cluster to the prod-user account on the production cluster—use the `kubectl config use-context` command:

```plaintext
kubectl config use-context prod-user@production
```

After running this command, your `current-context` in the kubeconfig file updates to `prod-user@production`. You can verify the change by viewing the configuration:

```plaintext
apiVersion: v1
kind: Config
current-context: prod-user@production
clusters:
- name: my-kube-playground
- name: development
- name: production
contexts:
- name: my-kube-admin@my-kube-playground
- name: prod-user@production
users:
- name: my-kube-admin
- name: prod-user
```

Additional variations of the `kubectl config` command let you update or delete entries within the kubeconfig file as needed.

## Configuring Namespaces

Each Kubernetes cluster can span multiple namespaces. You can designate a default namespace within a context so that switching contexts automatically sets the working namespace. For example, here is a configuration for the production cluster that sets "finance" as the default namespace:

```plaintext
apiVersion: v1
kind: Config
clusters:
- name: production
  cluster:
    certificate-authority: ca.crt
    server: https://172.17.0.51:6443
contexts:
- name: admin@production
  context:
    cluster: production
    user: admin
    namespace: finance
users:
- name: admin
  user:
    client-certificate: admin.crt
    client-key: admin.key
```

When you switch to the `admin@production` context, kubectl will automatically use the `finance` namespace.

## Working with Certificates in Kubeconfig

The kubeconfig file typically references certificate file paths. For clarity and robustness, it is best practice to use the full path to each certificate. Alternatively, you can embed the certificate data directly into the file by base64-encoding the certificate. For instance, instead of defining:

```plaintext
apiVersion: v1
kind: Config
clusters:
- name: production
  cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
```

you can embed the certificate data:

```plaintext
apiVersion: v1
kind: Config
clusters:
- name: production
  cluster:
    certificate-authority-data: LS0tLS1CRUdJTiBDRVJU...
```

If you encounter certificate data in base64 format and need to decode it, use the following command:

```plaintext
echo "LS0t...bnJ" | base64 --decode
```

This command will output the certificate in its standard PEM format:

```plaintext
-----BEGIN CERTIFICATE-----
MIICDCCAuCAQAoA...AIBDwAw...-----END CERTIFICATE-----
```

**<mark>Security Note</mark>**

Always ensure that certificate and key files are stored securely and access to the kubeconfig file is restricted to trusted users.

# Solution KubeConfig

In this guide, we'll walk through a comprehensive lab exercise focused on exploring the kubeconfig file and switching contexts for kubectl. We'll inspect the default kubeconfig file’s clusters, users, and contexts, troubleshoot a certificate issue, and finally apply changes to set a new kubeconfig file as the default configuration.

---

## Locating the Default kubeconfig File

First, determine the location of your default kubeconfig file by leveraging the HOME environment variable. The default file is stored in the hidden `.kube` directory:

```plaintext
root@controlplane ~ # echo $HOME
/root
root@controlplane ~ # pwd
/root
root@controlplane ~ # ls .kube/
cache  config
root@controlplane ~ # ls .kube/config
.kube/config
```

Viewing the file reveals that it contains one cluster, one user, and one context:

```plaintext
root@controlplane ~ # cat .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: LS0t... (truncated for brevity)
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
```

Thus, the default kubeconfig file located at `/root/.kube/config` defines:

* A single cluster named "kubernetes"
    
* One context named "kubernetes-admin@kubernetes"
    
* One user named "kubernetes-admin"
    

---

## Examining kubeconfig Details

Review the kubeconfig file to answer the following:

1. **Number of Clusters:**
    
    * The kubeconfig defines one cluster.
        
2. **Number of Users:**
    
    * Only one user is configured, which includes certificate information.
        
3. **Number of Contexts:**
    
    * There is a single context defined.
        
4. **User Configured in the Current Context:**
    
    * The current context "kubernetes-admin@kubernetes" specifies the user "kubernetes-admin."
        
        Note
        
        Although the context name might suggest a naming convention, always inspect the actual `user` field.
        
5. **Name of the Cluster in the Default Config:**
    
    * The cluster is named "kubernetes."
        

For clarity, here is a more detailed excerpt from the default kubeconfig file:

```plaintext
root@controlplane ~ # cat .kube/config
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: L0tSL1CRDJtiBRDVSUJZQ0FURS0tL0tSL0tCkt1SJmVakNDQWhZ0F3SUJBZ0lCQURBTkJn...
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
```

---

## Creating and Inspecting a New kubeconfig File

A new kubeconfig file named "my kube config" has been created in the root directory. This configuration file contains multiple clusters, contexts, and users. Below is the complete configuration:

```plaintext
contexts:
- name: test-user@development
  context:
    cluster: development
    user: test-user


- name: aws-user@kubernetes-on-aws
  context:
    cluster: kubernetes-on-aws
    user: aws-user


- name: test-user@production
  context:
    cluster: production
    user: test-user


- name: research
  context:
    cluster: test-cluster-1
    user: dev-user


users:
- name: test-user
  user:
    client-certificate: /etc/kubernetes/pki/users/test-user/test-user.crt
    client-key: /etc/kubernetes/pki/users/test-user/test-user.key


- name: dev-user
  user:
    client-certificate: /etc/kubernetes/pki/users/dev-user/developer-user.crt
    client-key: /etc/kubernetes/pki/users/dev-user/dev-user.key


- name: aws-user
  user:
    client-certificate: /etc/kubernetes/pki/users/aws-user/aws-user.crt
    client-key: /etc/kubernetes/pki/users/aws-user/aws-user.key


current-context: test-user@development
preferences: {}
```

### Key Details in the New Configuration

* **Clusters:**  
    The file defines a total of 4 clusters.
    
* **Contexts:**  
    There are 4 contexts configured.
    
* **User in the "research" Context:**  
    The "research" context uses the "dev-user."
    
* **AWS User’s Client Certificate:**  
    The AWS user’s client certificate is sourced from `aws-user.crt`.
    
* **Current Context:**  
    Initially set to "test-user@development."
    

---

## Switching Context to Use the Dev User

To access "test-cluster-1" using the "research" context (which utilizes the dev user), run the following command. Be sure to specify the kubeconfig file containing the desired configuration:

```plaintext
root@controlplane ~ ⟶ kubectl config use-context research --kubeconfig /root/my-kube-config
Switched to context "research".
root@controlplane ~ ⟶
```

Running `kubectl config view` should now indicate that the current context is "research."

---

## Setting the New kubeconfig File as Default

To avoid specifying the kubeconfig file with each command, move the new configuration file to the default location (`/root/.kube/config`). The updated file appears as follows:

```plaintext
name: production
clusters:
- cluster:
    certificate-authority: /etc/kubernetes/pki/ca.crt
    server: https://controlplane:6443
  name: test-cluster-1
contexts:
- context:
    cluster: kubernetes-on-aws
    user: aws-user
  name: aws-user@kubernetes-on-aws
- context:
    cluster: test-cluster-1
    user: dev-user
  name: research
- context:
    cluster: development
    user: test-user
  name: test-user@development
- context:
    cluster: production
    user: test-user
  name: test-user@production
current-context: research
kind: Config
preferences: {}
users:
- name: aws-user
  user:
    client-certificate: /etc/kubernetes/pki/users/aws-user/aws-user.crt
    client-key: /etc/kubernetes/pki/users/aws-user/aws-user.key
- name: dev-user
  user:
    client-certificate: /etc/kubernetes/pki/users/dev-user/developer-user.crt
    client-key: /etc/kubernetes/pki/users/dev-user/dev-user.key
- name: test-user
  user:
    client-certificate: /etc/kubernetes/pki/users/test-user/test-user.crt
    client-key: /etc/kubernetes/pki/users/test-user/test-user.key
```

Open the file in your preferred editor to confirm that all changes are in place and that the current context is correctly set to "research."

---

## Troubleshooting a Certificate Error

With the current context set to "research," you might run into a certificate error when trying to access the cluster:

```plaintext
root@controlplane ~ ➜  kubectl get nodes
error: unable to read client-cert /etc/kubernetes/pki/users/dev-user/developer-user.crt for dev-user due to open /etc/kubernetes/pki/users/dev-user/developer-user.crt: no such file or directory
```

Inspect the certificate directory to verify file names:

```plaintext
root@controlplane ~ ⟶ ls /etc/kubernetes/pki/users/
aws-user  dev-user  test-user
root@controlplane ~ ⟶ ls /etc/kubernetes/pki/users/dev-user/
dev-user.crt  dev-user.csr  dev-user.key
```

The error is due to the configuration mistakenly referring to the certificate as `developer-user.crt` instead of the correct `dev-user.crt`.

### Fixing the Issue

Update the "dev-user" entry in your kubeconfig file by changing:

```plaintext
client-certificate: /etc/kubernetes/pki/users/dev-user/developer-user.crt
```

to:

```plaintext
client-certificate: /etc/kubernetes/pki/users/dev-user/dev-user.crt
```

After saving, verify the fix by running:

```plaintext
root@controlplane ~  # kubectl get nodes
NAME           STATUS   ROLES                    AGE   VERSION
controlplane   Ready    control-plane,master     25m   v1.23.0
root@controlplane ~  #
```

The command should now list the nodes, confirming that the configuration is successful.

# API Groups

Before diving into authorization, it's essential to understand how API groups are organized in Kubernetes.

## Understanding the Kubernetes API

The Kubernetes API serves as the central interface for interacting with the cluster. Whether you use the kubectl command-line tool or make direct REST calls, every operation in the cluster is handled by the API server. For example, to check the cluster version, you can query the API server running on the master node (default port 6443) by appending the API version to the URL. Similarly, to list all the pods, you would send a request to `/api/v1/pods`.

Below is an example command that retrieves the Kubernetes version:

```plaintext
curl https://kube-master:6443/version
```

The expected output is:

```plaintext
{
  "major": "1",
  "minor": "13",
  "gitVersion": "v1.13.0",
  "gitCommit": "ddd47ac13c1a9483ea035a79cd7c1005ff21a6d",
  "gitTreeState": "clean",
  "buildDate": "2018-12-03T20:56:12Z",
  "goVersion": "go1.11.2",
  "compiler": "gc",
  "platform": "linux/amd64"
}
```

## API Groups Overview

Kubernetes organizes its API functionalities into various groups, making it easier to manage and scale operations. These groups include endpoints for version information, metrics, health checks, logs, and more. For instance, while the `/version` API reveals the cluster version, the `/metrics` and `/healthz` endpoints help monitor your cluster's health. The `/logs` endpoint can be integrated with third-party logging tools.

![The image shows six colored labels with text: /metrics, /healthz, /version, /api, /apis, and /logs, likely representing API endpoints.](https://kodekloud.com/kk-media/image/upload/v1752871267/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Groups/frame_70.jpg align="left")

Kubernetes APIs can be broadly divided into two categories:

1. **Core API Group**: Contains fundamental components such as namespaces, pods, replication controllers, events, endpoints, nodes, bindings, persistent volumes, persistent volume claims, config maps, secrets, and services.
    
2. **Named API Groups**: Organizes newer features into groups like apps, extensions, networking, storage, authentication, and authorization. For example, the "apps" group includes Deployments, ReplicaSets, and StatefulSets, while the "networking" group covers Network Policies. Other functionalities such as Certificate Signing Requests belong to different named groups.
    

![The image shows a hierarchical structure of a core API, detailing endpoints like namespaces, pods, and services under version v1.](https://kodekloud.com/kk-media/image/upload/v1752871268/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Groups/frame_120.jpg align="left")

Each resource in these API groups supports a collection of actions—referred to as "verbs"—that allow you to perform operations like list, get, create, delete, update, and watch.

![The image is a diagram illustrating Kubernetes API groups, resources, and actions like list, get, create, delete, update, and watch.](https://kodekloud.com/kk-media/image/upload/v1752871269/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Groups/frame_170.jpg align="left")

For a detailed overview of each API object, including its associated group and version, check out the Kubernetes API reference. For example, "v1 core" indicates the core API group at version v1. You can also explore your cluster’s API groups by accessing the API server on port 6443 without providing any specific path:

```plaintext
curl http://localhost:6443 -k
```

The response may resemble:

```plaintext
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    "/healthz",
    "/logs",
    "/metrics",
    "/openapi/v2",
    "/swagger-2.0.0.json"
  ]
}
```

## Accessing the Kubernetes API

When making direct requests to the Kubernetes API using curl, you might face authentication restrictions. For example, executing the following command without proper certificates might lead to a Forbidden error:

```plaintext
curl http://localhost:6443 -k
```

Output:

```plaintext
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "Forbidden: User \"system:anonymous\" cannot get path \"/\"",
  "reason": "Forbidden",
  "details": {},
  "code": 403
}
```

Authentication Tip

To access the API securely, include your certificate files in the curl command as follows:

```plaintext
curl http://localhost:6443 -k --key admin.key --cert admin.crt --cacert <ca.crt>
```

Alternatively, you can use the `kubectl proxy` command. This command starts a local proxy service on port 8001, utilizing the credentials from your kubeconfig file to authenticate your requests—eliminating the need to explicitly specify certificates.

Start the proxy by running:

```plaintext
kubectl proxy
```

This command outputs:

```plaintext
Starting to serve on 127.0.0.1:8001
```

Then, access the API via the proxy:

```plaintext
curl http://localhost:8001 -k
```

Expected response:

```plaintext
{
  "paths": [
    "/api",
    "/api/v1",
    "/apis",
    "/apis/",
    "/healthz",
    "/logs",
    "/metrics",
    "/openapi/v2",
    "/swagger-2.0.0.json"
  ]
}
```

**<mark>Important Distinction</mark>**

Do not confuse `kubectl proxy` with `kube-proxy`. The `kube-proxy` manages networking and connectivity between pods across nodes, while `kubectl proxy` forwards API requests using your kubeconfig credentials.

![The image shows a comparison indicating that "Kube proxy" is not equal to "Kubectl proxy."](https://kodekloud.com/kk-media/image/upload/v1752871270/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Groups/frame_300.jpg align="left")

## Summary

Kubernetes organizes its resources within various API groups, divided into the core API group and named API groups. Each group contains specific resources, and these resources support multiple actions (verbs) such as list, get, create, delete, update, and watch. In the next lesson on authorization, you will learn how these verbs control access within the cluster.

# Authorization

In previous lessons, we explored authentication and demonstrated how users or processes gain access to a cluster. With access granted, the next important question is: what actions can they perform? This is where authorization comes into play.

As a cluster administrator, you hold full control over operations such as viewing cluster objects (e.g., Pods, Nodes, and Deployments) and modifying them. For example:

```plaintext
kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   1/1     Running   0          53s


kubectl get nodes
NAME        STATUS   ROLES    AGE     VERSION
worker-1    Ready    <none>   5d21h   v1.13.0
worker-2    Ready    <none>   5d21h   v1.13.0
```

Administrators can also create or delete objects. For instance, to remove a node from the cluster:

```plaintext
kubectl delete node worker-2
Node worker-2 Deleted!
```

<mark>Minimum Privilege Principle</mark>

It is best practice to grant users only the minimum required permissions. For example, a developer might be allowed to deploy applications but should not have permissions to modify cluster-wide configurations like nodes, storage, or networking. Similarly, service accounts should be configured only with the necessary permissions.

Consider the following interactions for a user with restricted privileges:

```plaintext
kubectl get pods
Error from server (Forbidden): pods is forbidden: User "Bot-1" cannot list "pods"


kubectl get nodes
Error from server (Forbidden): nodes is forbidden: User "Bot-1" cannot get "nodes"


kubectl delete node worker-2
Error from server (Forbidden): nodes "worker-2" is forbidden: User "developer" cannot delete resource "nodes"
```

Authorization in Kubernetes is implemented through multiple mechanisms, including Node authorization, Attribute-Based Access Control (ABAC), Role-Based Access Control (RBAC), and Webhook modes. Each mechanism plays a distinct role in managing access control:

![The image lists four authorization mechanisms: Node, ABAC, RBAC, and Webhook, under the title "Authorization Mechanisms."](https://kodekloud.com/kk-media/image/upload/v1752871283/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authorization/frame_130.jpg align="left")

The Kubernetes API server is not only accessed by administrators but also by kubelets (nodes) that report the status of nodes and pods. Kubelets use a special node authorizer to handle requests from users whose names are prefixed by "system:node" and who belong to the "system:nodes" group. This node authorizer validates each request based on naming conventions and group memberships, ensuring that node-level actions are properly verified. Below is an overview of this authorization workflow:

![The image illustrates the Node Authorizer process in Kubernetes, showing interactions between a user, Kube API, and kubelet, with read/write permissions and a certificate.](https://kodekloud.com/kk-media/image/upload/v1752871283/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authorization/frame_180.jpg align="left")

For external API access, ABAC lets you tie individual users or groups to a set of permissions. For example, you can define a policy in a JSON file to allow a developer to view, create, and delete pods:

```plaintext
{"kind": "Policy", "spec": {"user": "dev-user", "namespace": "*", "resource": "pods", "apiGroup": "*"}}
{"kind": "Policy", "spec": {"user": "dev-user-2", "namespace": "*", "resource": "pods", "apiGroup": "*"}}
{"kind": "Policy", "spec": {"group": "dev-users", "namespace": "*", "resource": "pods", "apiGroup": "*"}}
{"kind": "Policy", "spec": {"user": "security-1", "namespace": "*", "resource": "csr", "apiGroup": "*"}}
```

However, changes to a user's permissions require editing the policy file and restarting the Kubernetes API server, making ABAC somewhat cumbersome. This is why many organizations prefer Role-Based Access Control (RBAC).

RBAC simplifies management by allowing the creation of roles that bundle specific permissions, which can then be assigned to users or groups. For example, you can create a role for developers that permits application deployments without granting access to modify cluster configurations, while a separate role for security personnel might include permissions to approve certificate signing requests (CSRs). The structure of these RBAC roles is summarized in the following image:

![The image illustrates RBAC roles, showing user permissions for developers and security, including actions like viewing, creating, and deleting PODs, and approving CSRs.](https://kodekloud.com/kk-media/image/upload/v1752871284/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Authorization/frame_290.jpg align="left")

In addition to the built-in mechanisms, you can delegate authorization to external tools such as [**Open Policy Agent (OPA)**](https://www.openpolicyagent.org/). With OPA, Kubernetes sends an API call containing user details and the request to an external service, which then decides whether the request should be approved.

Kubernetes also provides two simple modes: AlwaysAllow and AlwaysDeny. As their names imply, AlwaysAllow permits all requests without checks, whereas AlwaysDeny rejects all requests. These modes are configured via the authorization mode option when starting the Kubernetes API server. If no option is specified, the server defaults to AlwaysAllow.

For example, here is how you configure the AlwaysAllow mode:

```plaintext
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=${INTERNAL_IP} \
  --allow-privileged=true \
  --apiserver-count=3 \
  --authorization-mode=AlwaysAllow \
  --bind-address=0.0.0.0 \
  --enable-swagger-ui=true \
  --etcd-cafile=/var/lib/kubernetes/ca.pem \
  --etcd-certfile=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --etcd-keyfile=/var/lib/kubernetes/apiserver-etcd-client.key \
  --etcd-servers=https://127.0.0.1:2379 \
  --event-ttl=1h \
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \
  --kubelet-client-certificate=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --kubelet-client-key=/var/lib/kubernetes/apiserver-etcd-client.key \
  --service-node-port-range=30000-32767 \
  --client-ca-file=/var/lib/kubernetes/ca.pem \
  --tls-cert-file=/var/lib/kubernetes/apiserver.crt \
  --tls-private-key-file=/var/lib/kubernetes/apiserver.key \
  -v=2
```

Alternatively, you can configure multiple authorization modes at once with a comma-separated list. The following configuration enables Node, RBAC, and Webhook modes:

```plaintext
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=${INTERNAL_IP} \
  --allow-privileged=true \
  --apiserver-count=3 \
  --authorization-mode=Node,RBAC,Webhook \
  --bind-address=0.0.0.0 \
  --enable-swagger-ui=true \
  --etcd-cafile=/var/lib/kubernetes/ca.pem \
  --etcd-certfile=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --etcd-keyfile=/var/lib/kubernetes/apiserver-etcd-client.key \
  --etcd-servers=https://127.0.0.1:2379 \
  --event-ttl=1h \
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \
  --kubelet-client-certificate=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --kubelet-client-key=/var/lib/kubernetes/apiserver-etcd-client.key \
  --service-node-port-range=30000-32767 \
  --client-ca-file=/var/lib/kubernetes/ca.crt \
  --tls-cert-file=/var/lib/kubernetes/apiserver.crt \
  --tls-private-key-file=/var/lib/kubernetes/apiserver.key \
  --v=2
```

When multiple authorization modes are enabled, each incoming request is processed sequentially in the specified order. For example, the request is first evaluated by the Node authorizer; if the request is either not applicable or denied, it then passes to the next module (such as RBAC). As soon as one module approves the request, the evaluation stops and access is granted.

# Role Based Access Controls

In this lesson, we dive into Kubernetes Role-Based Access Control (RBAC), providing step-by-step instructions on how to create and manage roles and role bindings within a namespace. You'll learn how to define specific permissions for users, bind these permissions to users, and verify their access using kubectl commands.

## Creating a Role

To start, you need to define a Role object. Create a YAML file (e.g., developer-role.yaml) and set the API version to [`rbac.authorization.k8s.io/v1`](http://rbac.authorization.k8s.io/v1) and the kind to `Role`. In this example, we define a role named "developer" with permissions that allow developers to manage pods and create configmaps. Each permission rule contains three key sections: API groups, resources, and verbs. For resources in the core API group, leave the API group field blank.

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["list", "get", "create", "update", "delete"]
- apiGroups: [""]
  resources: ["configmaps"]
  verbs: ["create"]
```

Create the role using the following command:

```plaintext
kubectl create -f developer-role.yaml
```

**<mark>Note</mark>**

Remember that roles in Kubernetes are namespace-specific. Ensure the YAML definition targets the correct namespace if needed.

## Binding a User to the Role

Once the role is defined, you need to link a user to this role by creating a RoleBinding. A RoleBinding associates a user with the specified role within a given namespace. In this example, we'll create a role binding named `devuser-developer-binding` that assigns the "developer" role to the user `dev-user` in the default namespace.

Below is the YAML definition for the role binding (e.g., devuser-developer-binding.yaml):

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: devuser-developer-binding
subjects:
- kind: User
  name: dev-user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: developer
  apiGroup: rbac.authorization.k8s.io
```

Create the role binding with the command:

```plaintext
kubectl create -f devuser-developer-binding.yaml
```

## Verifying Roles and Role Bindings

After creating the role and role binding, you can verify them using the following kubectl commands:

* To list roles:
    
    ```plaintext
    kubectl get roles
    ```
    
    Example output:
    
    ```plaintext
    NAME         AGE
    developer    4s
    ```
    
* To list role bindings:
    
    ```plaintext
    kubectl get rolebindings
    ```
    
    Example output:
    
    ```plaintext
    NAME                        AGE
    devuser-developer-binding   24s
    ```
    

To get detailed information about the role, run:

```plaintext
kubectl describe role developer
```

Sample output:

```plaintext
Name:         developer
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources      Non-Resource URLs  Resource Names  Verbs
  --------       ------------------  ---------------  ----
  configmaps    []                  []               [create]
  pods          []                  []               [get watch list create delete]
```

Similarly, to examine the role binding details, use:

```plaintext
kubectl describe rolebinding devuser-developer-binding
```

Expected output:

```plaintext
Name:                   devuser-developer-binding
Labels:                 <none>
Annotations:            <none>
Role:
  Kind:                Role
  Name:                developer
Subjects:
  Kind   Name       Namespace
  ----   ----       ---------
  User   dev-user
```

## Testing Access Permissions

You can test whether a user has access to specific Kubernetes resources using the `kubectl auth can-i` command:

```plaintext
kubectl auth can-i create deployments
```

Expected output:

```plaintext
yes
```

And if you test deleting nodes:

```plaintext
kubectl auth can-i delete nodes
```

Expected output:

```plaintext
no
```

If you need to simulate actions as another user, use the `--as` flag. Even though the developer role does not have permission to create deployments, it can create pods:

```plaintext
kubectl auth can-i create deployments --as dev-user
```

Expected output:

```plaintext
no
```

```plaintext
kubectl auth can-i create pods --as dev-user
```

Expected output:

```plaintext
yes
```

You can also specify a particular namespace using the `--namespace` flag if the permissions are scoped accordingly.

## Granting Access to Specific Resources

In some cases, you might want to restrict a user's permissions to specific resources. For instance, if you need to allow a user to manage only two pods named "blue" and "orange" within a namespace, refine the role rule by including the `resourceNames` field:

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: developer
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "create", "update"]
  resourceNames: ["blue", "orange"]
```

**<mark>Warning</mark>**

Be cautious when specifying resource names. Only include the exact resources you intend to allow access to, as this will restrict access to other resources of the same type.

## Conclusion

This lesson has demonstrated how to set up and manage RBAC in Kubernetes. By creating roles and role bindings, you can control user permissions precisely within a namespace. For further practice, refer to Kubernetes' official documentation and experiment with real RBAC configurations in your environment.

# Solution Role Based Access Controls

In this lab, we explore role-based access controls (RBAC) in Kubernetes. The tutorial covers inspecting the API server configuration, reviewing roles and permissions, and creating the necessary roles and bindings for a development user. Follow along to gain hands-on experience with securing your Kubernetes cluster.

──────────────────────────────

## Inspecting the API Server Configuration

Start by inspecting the kube-apiserver manifest to identify the configured authorization modes. In the manifest snippet below, note the use of "Node,RBAC" for the authorization mode:

```plaintext
creationTimestamp: null
labels:
  component: kube-apiserver
  tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.48.174.6
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --requestheader-allowed-names=front-proxy-client
    - --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
    - --requestheader-username-headers=X-Remote-User
    - --secure-port=6443
    - --service-account-issuer=https://kubernetes.default.svc.cluster.local
    - --service-account-key-file=/etc/kubernetes/pki/sa.pub
    - --service-account-signing-key-file=/etc/kubernetes/pki/sa.key
    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
  image: k8s.gcr.io/kube-apiserver:v1.23.0
  imagePullPolicy: IfNotPresent
  livenessProbe:
```

An alternative verification method is to inspect the running processes on the control plane. For instance, execute the following command:

```plaintext
root@controlplane ~ ps aux | grep authorization
root      3403  0.0  0.0  830588 115420 ?        Ssl  22:54   0:55 kube-controller-manager --allocate-node-authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/etc/kubernetes/pki/ca.crt --cluster-cidr=10.244.0.0/16 --kubelet-client-certificate=/etc/kubernetes/pki/ca.key --controllers=*,bootstrapsigner,tokens --kubelet-client-key=/etc/kubernetes/pki/front-proxy-client.key --service-account-privileged=false --service-cluster-ip-range=10.96.0.0/12 --use-service-account-credentials=true
root      3614  0.0  0.0  759136  55292 ?        Ssl  22:55   0:10 kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=true
root      3630  0.0  0.1 111896  316984 ?        S    22:55   2:07 kube-apiserver --advertise-address=10.4.8.174.6 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=X-Remote-Group --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-client-ca.crt --requestheader-headers-prefix=X-Remote-Extra --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=6443 --service-account-issuer=https://kubernetes.default.svc.cluster.local --service-account-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
root      25283  0.0  0.0  13444  1068 pts/0    S+   23:40   0:00 grep --color=auto authorization
```

The output confirms that the kube-apiserver is running with the "Node,RBAC" authorization mode.

──────────────────────────────

## Reviewing Roles in the Cluster

Begin by identifying the existing roles within the default namespace. The output below shows that no roles exist in that namespace:

```plaintext
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubectl.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.48.174.6:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.48.174.6
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    - --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    - --request-header-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    - --requestheader-extra-headers-prefix=X-Remote-Extra-
    - --requestheader-group-headers=X-Remote-Group
```

To count the roles across all namespaces, use this command:

```plaintext
root@controlplane ~ k get roles -A --no-headers | wc -l
12
```

There are 12 roles configured cluster-wide.

──────────────────────────────

## Examining the kube-proxy Role

Next, examine the permissions assigned to the kube-proxy role in the kube-system namespace. Run the following command to display its details:

```plaintext
root@controlplane ~ ✗ k describe role kube-proxy -n kube-system
Name:         kube-proxy
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources           Non-Resource URLs  Resource Names  Verbs
  ---------           -----------------  ---------------  -----
  configmaps          []                                   [get]
```

From the output, the kube-proxy role is limited to performing the "get" action on ConfigMap resources only. It cannot delete or update ConfigMaps.

──────────────────────────────

## Checking Role Bindings for kube-proxy

Identify which service account is bound to the kube-proxy role by examining the role bindings in the kube-system namespace:

```plaintext
root@controlplane ~ ✗ k describe role kube-proxy -n kube-system
Name:         kube-proxy
Labels:       <none>
Annotations:  <none>
PolicyRules:
  Resources      Non-Resource URLs  Resource Names  Verbs
  -----------    -----------------  --------------  -----
  configmaps     []                  [kube-proxy]    [get]
```

List the role bindings in the kube-system namespace:

```plaintext
root@controlplane ~ ✗ k get rolebindings -n kube-system
NAME                                              AGE
kube-proxy                                        48m
kubeadm:kubelet-config-1.23                       48m
kubeadm:nodes-kubeadm-config                      48m
system:extension-apiserver-authentication-reader  48m
system:leader-locking-kube-controller-manager     48m
system:leader-locking-kube-scheduler               48m
system:controller:bootstrap-signer               48m
system:controller:cloud-provider                  48m
system:controller:token-cleaner                   48m
```

Then, inspect the kube-proxy role binding:

```plaintext
root@controlplane ~ ◦ k describe rolebindings kube-proxy -n kube-system
Name:         kube-proxy
Labels:       <none>
Annotations:  <none>
Role:
  Kind: Role
  Name: kube-proxy
Subjects:
  Kind   Name
  ----   ----
  Group  system:bootstrappers:kubeadm:default-node-token
```

This confirms that the kube-proxy role is associated with the group "system:bootstrappers:kubeadm:default-node-token".

──────────────────────────────

## Inspecting the kubeconfig and User Permissions

Review the kubeconfig file to verify the configured users on the cluster:

```plaintext
root@controlplane ~ ⮀ kubectl config view
apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: DATA+OMITTED
    server: https://controlplane:6443
  name: kubernetes
contexts:
- context:
    cluster: kubernetes
    user: kubernetes-admin
  name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: dev-user
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
- name: kubernetes-admin
  user:
    client-certificate-data: REDACTED
    client-key-data: REDACTED
```

Test the permissions for the "dev-user" by listing pods in the default namespace:

```plaintext
root@controlplane ~ ⮀ k get pods --as dev-user
Error from server (Forbidden): pods is forbidden: User "dev-user" cannot list resource "pods" in API group "" in the namespace "default"
```

This error confirms that the "dev-user" currently lacks permission to list pods in the default namespace.

──────────────────────────────

## Creating Roles and Role Bindings for the dev-user

Before proceeding, review how to create a role in Kubernetes. The following command creates a role named "developer" granting list, create, and delete permissions on pods. Notice that the correct flag is --resource (singular):

```plaintext
root@controlplane ~ ➜ k create role developer --verb=list,create,delete --resource=pods
role.rbac.authorization.k8s.io/developer created
```

You can verify the created role by describing it:

```plaintext
root@controlplane ~ ➜ k describe role developer
Name:         developer
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources  Non-Resource URLs  Resource Names  Verbs
  ---------  -----------------  --------------  --------------
  pods       []                 []              [list create delete]
```

Next, assign the "developer" role to "dev-user" by creating a role binding:

```plaintext
root@controlplane ~ ⟩ k create rolebinding dev-user-binding --role=developer --user=dev-user
rolebinding.rbac.authorization.k8s.io/dev-user-binding created
```

Verify the role binding:

```plaintext
root@controlplane ~ ⟩ k describe rolebinding dev-user-binding
Name:         dev-user-binding
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  Role
  Name:  developer
Subjects:
  Kind   Name
  ----   ----
  User   dev-user
```

──────────────────────────────

## Troubleshooting Access in the Blue Namespace

Suppose the dev-user tries to retrieve details of a pod named "dark-blue-app" in the blue namespace:

```plaintext
k --as dev-user get pod dark-blue-app -n blue
```

The error received is:

```plaintext
Error from server (Forbidden): pods "dark-blue-app" is forbidden: User "dev-user" cannot get resource "pods" in API group "" in the namespace "blue"
```

Upon inspection, the blue namespace has an existing "developer" role and the "dev-user-binding" role binding:

```plaintext
root@controlplane ~ ✗ k get roles -n blue
NAME        CREATED AT
developer   2022-04-17T23:25:04Z


root@controlplane ~ ✗ k get rolebindings -n blue
NAME              ROLE             AGE
dev-user-binding  Role/developer   24m
```

Currently, the "developer" role in the blue namespace grants permission only for the pod "dark-blue-app." To provide the dev-user with the ability to manage that pod and to create new deployments, update the role to include rules for handling deployments in the "apps" API group. Below is an example of the updated role:

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  creationTimestamp: "2022-04-17T23:25:04Z"
  name: developer
  namespace: blue
  resourceVersion: "4534"
  uid: b1424ab8-9776-43b0-bddf-433dfff73c1e
rules:
- apiGroups:
  - ""
  resourceNames:
  - dark-blue-app
  resources:
  - pods
  verbs:
  - get
  - watch
  - create
  - delete
- apiGroups:
  - "apps"
  resources:
  - deployments
  verbs:
  - get
  - watch
  - create
  - delete
```

Save the changes, then test the configuration by creating a deployment as the dev-user:

```plaintext
root@controlplane ~ ⟫ k --as dev-user create deployment nginx --image=nginx -n blue
deployment.apps/nginx created
```

The successful creation of the deployment confirms that the necessary permissions have been granted.

**<mark>Note</mark>**

Always verify RBAC changes by testing user permissions in the designated namespace to ensure that the intended access is provided while maintaining security.

# Cluster Roles

In earlier sections, we discussed namespaced resources and how roles and role bindings work within a specific namespace. By default, if you don't explicitly specify a namespace, these resources are created in the default namespace. Namespaced resources include pods, replica sets, deployments, services, secrets, and more. In contrast, some Kubernetes objects such as nodes, persistent volumes, certificate signing requests, and namespaces are classified as cluster-scoped resources, meaning they exist across the entire cluster rather than within a single namespace.

![The image illustrates Kubernetes resources categorized into "Namespaced" and "Cluster Scoped" groups, showing different components like pods, nodes, and roles.](https://kodekloud.com/kk-media/image/upload/v1752871285/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Cluster-Roles/frame_100.jpg align="left")

To inspect the full list of available resources, you can run the following commands in your terminal:

```plaintext
kubectl api-resources --namespaced=true
```

```plaintext
kubectl api-resources --namespaced=false
```

When managing access for namespaced resources, roles and role bindings are typically sufficient. However, for cluster-scoped resources such as nodes or persistent volumes, you need to configure cluster roles and cluster role bindings. For example, a cluster role can be created for a cluster administrator to view, create, or delete nodes, while a storage administrator might require a cluster role to manage persistent volumes and persistent volume claims.

![The image outlines cluster roles: "Cluster Admin" can view, create, and delete nodes; "Storage Admin" can view, create PVs, and delete PVCs.](https://kodekloud.com/kk-media/image/upload/v1752871286/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Cluster-Roles/frame_170.jpg align="left")

To define a cluster role, create a ClusterRole definition file that specifies the rules for the resources you wish to manage. Consider the following example, which grants permissions for managing nodes:

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-administrator
rules:
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["list", "get", "create", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-admin-role-binding
subjects:
- kind: User
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: cluster-administrator
  apiGroup: rbac.authorization.k8s.io
```

This YAML file first defines the cluster role "cluster-administrator" with specific permissions for nodes. It then creates a ClusterRoleBinding that ties this role to the "cluster-admin" user. To apply these configurations to your cluster, run:

```plaintext
kubectl create -f <filename>.yaml
```

**<mark>Important Note</mark>**

While cluster roles and bindings are mainly intended for cluster-scoped resources, they can also be used for namespaced resources. When a cluster role is applied to namespaced resources, the permissions are effective across all namespaces, unlike a namespaced role which restricts access to a specific namespace.

![The image illustrates Kubernetes cluster roles, differentiating between namespaced resources (e.g., pods, services) and cluster-scoped resources (e.g., nodes, clusterroles).](https://kodekloud.com/kk-media/image/upload/v1752871287/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Cluster-Roles/frame_240.jpg align="left")

Kubernetes automatically generates several cluster roles during the cluster setup. In subsequent sections, we will dive deeper into these default roles and how you can leverage them.

# Solution Cluster Roles

In this lesson, we will explore the concepts of cluster roles and cluster role bindings. We will inspect the existing cluster roles and their bindings, count them, and review their permissions before creating new roles and bindings for a new team member named Michelle.

---

## Inspecting Existing Cluster Roles

Begin by examining the currently defined cluster roles in your cluster. These roles include `cluster-admin`, system-specific roles, and others. Run the following command to list them:

```plaintext
system:volume-scheduler
system:certificates.k8s.io:legacy-unknown-approver
system:certificates.k8s.io:kubelet-serving-approver
system:certificates.k8s.io:kube-apiserver-client-approver
system:certificates.k8s.io:kube-apiserver-client-kubelet-approver
system:service-account-issuer-discovery
system:node-proxier
system:kube-scheduler
system:controller:attacheddetach-controller
system:controller:clusteroverlay-aggregation-controller
system:controller:cronjob-controller
system:controller:daemon-set-controller
system:controller:deployment-controller
system:controller:endpoint-controller
system:controller:endpointslice-mirroring-controller
system:controller:expand-controller
system:controller:ephemeral-volume-controller
system:controller:generic-garbage-collector
system:controller:job-controller
system:controller:namespace-controller
system:controller:node-controller
system:controller:persistent-volume-binder
system:controller:garbage-collector
system:controller:replicaset-controller
system:controller:replication-controller
system:controller:resourcequota-controller
system:controller:service-account-controller
system:controller:service-controller
system:controller:statefulset-controller
system:controller:ttl-controller
system:controller:vertical-pod-autoscaler
system:controller:volume-protection-controller
system:controller:ttl-after-finished-controller
system:controller:root-ca-cert-publisher
k3s-cloud-controller-manager
```

You can count these roles manually by piping the output to `wc -l`. For example:

```plaintext
k get clusterroles --no-headers | wc -l
```

On one system, this command produced an output of 6, but keep in mind that this example shows a truncated list. In another instance, a total of 69 roles might be expected using a different counting method.

**<mark>Note</mark>**

Cluster roles are cluster-scoped and are not limited to any namespace.

---

## Inspecting Cluster Role Bindings

Next, verify the cluster role bindings by counting them with the following command:

```plaintext
k get clusterrolebindings --no-headers | wc -l
```

This command returned 54 on the system in question, indicating that there are 54 cluster role bindings.

To illustrate further, here is an excerpt displaying some cluster roles and their creation times:

```plaintext
k get clusterroles
NAME                                                                  CREATED AT
cluster-admin                                                       2022-04-18T00:01:23Z
system:discovery                                                   2022-04-18T00:01:23Z
system:monitoring                                                  2022-04-18T00:01:23Z
system:basic-user                                                  2022-04-18T00:01:23Z
system:public-info-viewer                                          2022-04-18T00:01:23Z
system:aggregate-to-admin                                          2022-04-18T00:01:23Z
system:aggregate-to-edit                                           2022-04-18T00:01:23Z
system:aggregate-to-view                                           2022-04-18T00:01:23Z
system:heapster                                                    2022-04-18T00:01:23Z
system:node                                                       2022-04-18T00:01:23Z
...
```

**<mark>Note</mark>**

Both cluster roles and cluster role bindings are applied across the entire cluster and are not namespace-specific.

---

## Reviewing Cluster Admin Role Bindings

The `cluster-admin` role represents the highest permission level and is bound to specific user groups via a ClusterRoleBinding. To identify the binding for the `cluster-admin` role, run:

```plaintext
k get clusterrolebindings | grep cluster-admin
```

The output may appear as follows:

```plaintext
cluster-admin                    12m   ClusterRole/cluster-admin
helm-kube-system-traefik         12m   ClusterRole/cluster-admin
helm-kube-system-traefik-crd     12m   ClusterRole/cluster-admin
```

You can then inspect the details of the cluster-admin binding with:

```plaintext
k describe clusterrolebindings cluster-admin
```

This command produces output similar to:

```plaintext
Name:         cluster-admin
Labels:       kubernetes.io/bootstraping=rbac-defaults
Annotations:  rbac.authorization.kubernetes.io/autoupdate: true
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind  Name                 Namespace
  ----- ------------------- -----------
  Group system:masters
```

This confirms that the `cluster-admin` role is bound by default to the `system:masters` group, thereby granting all possible operations (denoted by `[*]` for all actions on all resources).

**<mark>Warning</mark>**

Exercise caution when modifying cluster roles as they affect permissions across the entire cluster.

---

## Granting Node Access to a New User (Michelle)

Michelle, a new team member, requires access to manage nodes. Initially, when Michelle runs:

```plaintext
k get nodes --as michelle
```

she encounters the following error:

```plaintext
Error from server (Forbidden): nodes is forbidden: User "michelle" cannot list resource "nodes" in API group "" at the cluster scope
```

To resolve this issue, follow these steps:

### Create the Cluster Role for Node Access

Create a cluster role named `michelle-role` that allows Michelle to get, list, and watch nodes:

```plaintext
k create clusterrole michelle-role --verb=get,list,watch --resource=nodes
```

Verify the new role by describing it:

```plaintext
k describe clusterrole michelle-role
```

Expected output:

```plaintext
Name:         michelle-role
Labels:       <none>
Annotations:  <none>
PolicyRule:
  Resources   Non-Resource URLs   Resource Names   Verbs
  ---------   -----------------   ---------------  ---------
  nodes       []                  []               [get list watch]
```

### Bind the Cluster Role to Michelle

Bind the role to Michelle’s user account with:

```plaintext
k create clusterrolebinding michelle-role-binding --clusterrole=michelle-role --user=michelle
```

Verify this binding with:

```plaintext
k describe clusterrolebinding michelle-role-binding
```

Expected output:

```plaintext
Name:         michelle-role-binding
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  michelle-role
Subjects:
  Kind   Name      Namespace
  ----   ----      ---------
  User   michelle
```

Finally, test Michelle’s access again:

```plaintext
k get nodes --as michelle
```

A successful command execution will display a list of nodes similar to:

```plaintext
NAME         STATUS   ROLES                 AGE   VERSION
controlplane Ready    control-plane,master  16m   v1.23.3+k3s1
```

---

## Expanding Michelle's Responsibilities to Storage

Michelle’s roles are expanding to include storage management. To grant her access to storage-related resources, first verify the available API resources by running:

```plaintext
kubectl api-resources
```

This command lists resources along with their short names, API versions, and scope. For storage management, the key resources are persistent volumes and storage classes.

### Create a Cluster Role for Storage Administration

Create a cluster role named `storage-admin` that allows listing, creating, getting, and watching persistent volumes and storage classes:

```plaintext
k create clusterrole storage-admin --resource=persistentvolumes,storageclasses --verb=list,create,get,watch
```

Verify the role in YAML format using:

```plaintext
k get clusterrole storage-admin -o yaml
```

An expected YAML output is as follows:

```plaintext
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  creationTimestamp: "2022-04-18T00:20:48Z"
  name: storage-admin
  resourceVersion: "921"
  uid: e0ee52a7-b32c-4a42-bb7a-a783b040cd4e
rules:
- apiGroups:
  - ""
  resources:
  - persistentvolumes
  verbs:
  - list
  - create
  - get
  - watch
- apiGroups:
  - storage.k8s.io
  resources:
  - storageclasses
  verbs:
  - list
  - create
  - get
  - watch
```

### Bind the Storage Admin Role to Michelle

Bind the `storage-admin` role to Michelle with the following command:

```plaintext
k create clusterrolebinding michelle-storage-admin --user=michelle --clusterrole=storage-admin
```

Verify this binding:

```plaintext
k describe clusterrolebinding michelle-storage-admin
```

Expected output:

```plaintext
Name:         michelle-storage-admin
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  storage-admin
Subjects:
  Kind   Name      Namespace
  ----   ----      ---------
  User   michelle
```

Michelle should now have the necessary permissions to manage storage resources such as persistent volumes and storage classes.

# Admission Controllers

In this lesson, we’ll explore admission controllers—an essential component of Kubernetes that enforces additional security measures and modifies requests before they are persisted. Understanding how admission controllers work and how to configure them is key to enhancing your cluster’s security and operations.

When you use the kubectl utility to interact with a Kubernetes cluster (for example, to create a pod), the request first reaches the API server. The following sequence outlines the process:

1. The API server receives the request.
    
2. It authenticates the request—commonly using certificates specified in your KubeConfig file. For example, inspect your KubeConfig with:
    
    ```plaintext
    cat ~/.kube/config
    apiVersion: v1
    clusters:
    - cluster:
        certificate-authority-data: LS0t...
        # Truncated for brevity
    ```
    
3. Once authenticated, the request proceeds to the authorization phase where Kubernetes checks if the user has permission to perform the operation, typically using Role-Based Access Control (RBAC). For example, a developer role can be defined as follows:
    
    ```plaintext
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: developer
    rules:
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["list", "get", "create", "update", "delete"]
      resourceNames: ["blue", "orange"]
    ```
    
    In this example, the developer role is limited to operations on pods named "blue" or "orange". These RBAC rules operate solely at the API level to control permitted operations.
    

After authentication and authorization, you might need to enforce restrictions beyond what RBAC offers. Consider these scenarios:

* Preventing images from public Docker Hub by allowing only images from a specific internal registry.
    
* Enforcing that images never use the "latest" tag.
    
* Rejecting pod creation when a container is configured to run as the root user.
    
* Allowing specific capabilities (like "MAC\_ADMIN") only under certain conditions.
    
* Ensuring resource metadata always includes specific labels.
    

To address such policies, admission controllers are used. They operate after authentication and authorization but before the request is saved in etcd. Admission controllers can validate configurations, modify requests, or trigger additional operations.

Below is an overview of the Kubernetes process flow with admission controllers positioned between the authorization phase and resource creation:

![The image illustrates the Kubernetes process flow: Kubectl to Authentication, Authorization, Admission Controllers, and finally, Create Pod.](https://kodekloud.com/kk-media/image/upload/v1752871273/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Admission-Controllers/frame_210.jpg align="left")

## Built-in Admission Controllers

Kubernetes includes several built-in admission controllers, such as:

* **Always Pull Images:** Ensures that images are pulled every time a pod is created.
    
* **Default Storage Class:** Automatically assigns a default storage class to PersistentVolumeClaims (PVCs) when none is specified.
    
* **Event Rate Limit:** Throttles the number of requests processed by the API server to prevent overload.
    
* **Namespace Exists:** Rejects requests for resources in namespaces that do not exist.
    

### Example: Namespace Existence Check

Consider the "namespace exists" admission controller. When you attempt to create a pod in a namespace that doesn’t exist, for instance:

```plaintext
kubectl run nginx --image nginx --namespace blue
```

The API server goes through authentication and RBAC authorization. Then, the namespace exists controller checks for the "blue" namespace. Since it isn’t found, the request is rejected with an error similar to:

```plaintext
Error from server (NotFound): namespaces "blue" not found
```

**<mark>Namespace Auto-Provisioning</mark>**

Some clusters may have the namespace auto-provision admission controller enabled (disabled by default) to automatically create missing namespaces.

To list the admission controllers enabled by default, run:

```plaintext
kube-apiserver -h | grep enable-admission-plugins
```

If you’re using a kubeadm-based setup, execute this command inside the kube-apiserver control plane pod using the kubectl exec command.

## Configuring Admission Controllers

To add an admission controller, update the `--enable-admission-plugins` flag on the Kubernetes API server. In a kubeadm-based setup, modify the kube-apiserver manifest file accordingly. For example, to enable both NodeRestriction and NamespaceAutoProvision, update the ExecStart command as follows:

```plaintext
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=${INTERNAL_IP} \
  --allow-privileged=true \
  --apiserver-count=3 \
  --authorization-mode=Node,RBAC \
  --bind-address=0.0.0.0 \
  --enable-swagger-ui=true \
  --etcd-servers=https://127.0.0.1:2379 \
  --event-ttl=1h \
  --runtime-config=api/all \
  --service-cluster-ip-range=10.32.0.0/24 \
  --service-node-port-range=30000-32767 \
  --v=2 \
  --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
```

For clusters running the API server as a pod (common in kubeadm setups), the manifest might look like this:

```plaintext
apiVersion: vl
kind: Pod
metadata:
  creationTimestamp: null
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --authorization-mode=Node,RBAC
    - --advertise-address=172.17.0.107
    - --allow-privileged=true
    - --enable-bootstrap-token-auth=true
    - --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
    image: k8s.gcr.io/kube-apiserver-amd64:vl.11.3
    name: kube-apiserver
```

To disable specific admission controller plugins, use the `--disable-admission-plugins` flag similarly.

## Auto-Provisioning Example

With NamespaceAutoProvision enabled, running the following command:

```plaintext
kubectl run nginx --image nginx --namespace blue
```

passes through authentication and authorization. The auto-provision controller detects that the "blue" namespace is missing, creates it automatically, and the pod creation completes successfully. The output would be:

```plaintext
kubectl run nginx --image nginx --namespace blue
Pod/nginx created!
```

Verifying the existing namespaces:

```plaintext
kubectl get namespaces
NAME         STATUS   AGE
blue         Active   3m
default      Active   23m
kube-public  Active   24m
kube-system  Active   24m
```

This example shows that admission controllers not only validate and potentially reject requests but can also perform background operations and modify the request content as needed.

**<mark>Deprecated Controllers</mark>**

Note that the NamespaceAutoProvision and NamespaceExists admission controllers are deprecated. They have been replaced by the Namespace Lifecycle admission controller, which now ensures that requests to non-existent namespaces are rejected and protects default namespaces (such as default, kube-system, and kube-public) from deletion.

# Solution Admission Controllers

In this lesson, we will walk through the solutions for the admission controllers lab, explaining each step and providing the necessary commands and configuration snippets.

![The image shows a KodeKloud practice test interface with a question about admission controller functions and a terminal window.](https://kodekloud.com/kk-media/image/upload/v1752871290/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Solution-Admission-Controllers/frame_0.jpg align="left")

## Understanding Admission Controller Functions

For the first question, you need to determine which option is NOT a function of admission controllers. The correct answer is **"authenticate users"** because admission controllers operate after authentication has already taken place; they do not handle user authentication.

```plaintext
root@controlplane ~  []
```

## Verifying Enabled Admission Controllers

The next question asks which admission controller is not enabled by default. To find out, start by listing the pods in the `kube-system` namespace using the following command:

![The image shows a KodeKloud practice test interface with a terminal and a question about which admission controller is not enabled by default.](https://kodekloud.com/kk-media/image/upload/v1752871292/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Solution-Admission-Controllers/frame_30.jpg align="left")

```plaintext
root@controlplane ~ ➜ kubectl get pods -n kube-system
NAME                                         READY   STATUS    RESTARTS   AGE
coredns-74ff55c5b-88v8r                       1/1     Running   0          38m
coredns-74ff55c5b-tng7p                       1/1     Running   0          38m
etcd-controlplane                            1/1     Running   0          38m
kube-apiserver-controlplane                  1/1     Running   0          38m
kube-controller-manager-controlplane         1/1     Running   0          38m
kube-flannel-ds-wzlpx                         1/1     Running   0          38m
kube-proxy-hccb9                             1/1     Running   0          38m
kube-scheduler-controlplane                  1/1     Running   0          38m
```

Next, run this command to check the enabled admission plugins:

```plaintext
root@controlplane ~ ➜ kubectl exec -it kube-apiserver-controlplane -n kube-system -- kube-apiserver -h | grep 'enable-admission-pl'
```

The output lists default enabled plugins like `NamespaceLifecycle`, `LimitRanger`, `ServiceAccount`, among others. Notice that **"namespace auto-provision"** is not listed. Also, verifying the enabled webhooks gives you the following output:

```plaintext
root@controlplane ~ ➜ kubectl exec -it kube-apiserver-controlplane -n kube-system -- kube-apiserver -h | grep 'enable-admission-plugins'
--enable-admission-plugins strings   admission plugins that should be enabled in addition to default enabled ones (NamespaceLifecycle, LimitRanger, ServiceAccount, TaintNodesByCondition, Priority, DefaultTolerationSeconds, DefaultStorageClass, StorageObjectInUseProtection, PersistentVolumeClaimResize, VolumeClasses, CertificateApproval, CertificateSigning, CertificateSubjectRestrictions, DefaultIngressClass, DefaultTolerations, CertificateExemption, DefaultIngressClass, MutatingAdmissionWebhook, ValidatingAdmissionWebhook, ResourceQuota).
```

Since "namespace auto-provision" is missing from the default list, it is the correct answer for the question.

## Identifying Explicitly Enabled Admission Controllers

The following question asks which admission controller is enabled in the cluster but is generally disabled by default. You can inspect the kube-apiserver configuration file located at `/etc/kubernetes/manifests/kube-apiserver.yaml` to determine the enabled plugins. Use your preferred text editor or run the following search command:

```plaintext
root@controlplane ~ ➜ grep 'enable-admission-plugins' /etc/kubernetes/manifests/kube-apiserver.yaml
```

A snippet from the configuration file might appear as follows:

```plaintext
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.49.219.9:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.49.219.9
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
    - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
    - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
    - --etcd-servers=https://127.0.0.1:2379
    - --insecure-port=0
    - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
```

In this configuration, the `NodeRestriction` admission controller is explicitly enabled, despite normally being disabled.

**<mark>Note</mark>**

By inspecting the kube-apiserver configuration, you can verify which admission controllers are enabled and adjust your settings accordingly.

## Enabling Namespace Auto Provision

The next task is to create an NGINX pod in the "blue" namespace. However, the lab specifies that the "blue" namespace has not been created yet. Attempting to run the following command:

```plaintext
root@controlplane ~ ➜ kubectl run nginx --image nginx --rm
```

results in an error because the **exists admission controller** rejects requests for non-existent namespaces.

To automatically create the namespace during pod creation, enable the `NamespaceAutoProvision` admission controller. Edit the `/etc/kubernetes/manifests/kube-apiserver.yaml` file and update the `--enable-admission-plugins` flag. Insert `NamespaceAutoProvision` after `NodeRestriction` (separated by a comma). The updated configuration should look similar to this:

```plaintext
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.49.219.9:6443
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
      - kube-apiserver
      - --advertise-address=10.49.219.9
      - --allow-privileged=true
      - --authorization-mode=Node,RBAC
      - --client-ca-file=/etc/kubernetes/pki/ca.crt
      - --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
      - --enable-bootstrap-token-auth=true
      - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
      - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
      - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
      - --etcd-servers=https://127.0.0.1:2379
      - --insecure-port=0
      - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
      - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
      - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
      - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
```

After saving and allowing the API server to restart, create the NGINX pod in the blue namespace:

```plaintext
root@controlplane ~ ➜ kubectl run nginx --image nginx -n blue
pod/nginx created


root@controlplane ~ ➜ kubectl get ns
NAME              STATUS   AGE
blue              Active   7s
default           Active   50m
kube-node-lease   Active   50m
kube-public       Active   50m
kube-system       Active   50m
```

As you can see, the **blue namespace** is automatically created due to the enabled `NamespaceAutoProvision` admission controller.

## Disabling the Default Storage Class Controller

The final task is to disable the default storage class admission controller. To do this, update the `/etc/kubernetes/manifests/kube-apiserver.yaml` file by adding the `--disable-admission-plugins` flag on a new line immediately after the enabled plugins. For example:

```plaintext
apiVersion: v1
kind: Pod
metadata:
  annotations:
    kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.49.219.9:6443
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
      - kube-apiserver
      - --advertise-address=10.49.219.9
      - --allow-privileged=true
      - --authorization-mode=Node,RBAC
      - --client-ca-file=/etc/kubernetes/pki/ca.crt
      - --enable-admission-plugins=NodeRestriction,NamespaceAutoProvision
      - --disable-admission-plugins=DefaultStorageClass
      - --enable-bootstrap-token-auth=true
      - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt
      - --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt
      - --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key
      - --etcd-servers=https://127.0.0.1:2379
      - --insecure-port=0
      - --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
      - --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
      - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
      - --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
```

Once the API server restarts, validate the changes by inspecting the running process with a command like:

```plaintext
ps -ef | grep kube-apiserver
```

This command will list both the enabled and disabled admission plugins.

# Validating and Mutating Admission Controllers

In this lesson, we explore the different types of Admission Controllers in Kubernetes and demonstrate how to configure custom controllers. We cover both validating and mutating admission controllers, explaining their functions and how Kubernetes processes them during the request lifecycle.

## Overview of Admission Controllers

Admission Controllers are plugins that intercept requests to the Kubernetes API server before persisting an object. They are categorized into two main types:

1. **Validating Admission Controllers:**  
    These controllers examine incoming requests and reject them if they do not conform to specified policies. For example, a namespace existence validator checks if the requested namespace exists; if it doesn't, the request is rejected.
    
2. **Mutating Admission Controllers:**  
    These controllers modify incoming requests prior to their persistence. A common example is the default storage class admission controller. When you create a PersistentVolumeClaim (PVC) without specifying a storage class, this controller automatically adds the default storage class to the request.
    

For instance, consider the following PVC creation request:

```plaintext
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
```

This request undergoes authentication and authorization, then passes through the admission controllers. Because no storage class is specified, the default storage class admission controller mutates the request. When the PVC is created, inspecting it reveals the storage class added:

```plaintext
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi
  storageClassName: default
```

You can view the details of the PVC with:

```plaintext
kubectl describe pvc myclaim
Name:           myclaim
Namespace:      default
StorageClass:   default
Status:         Pending
Volume:         <none>
Labels:         <none>
Annotations:    <none>
```

**<mark>Note</mark>**

Mutating controllers adjust the request (by adding configurations like the storage class), while validating controllers strictly allow or deny requests based on predefined criteria. In many cases, mutating controllers are invoked before validating ones to ensure that any modifications are considered during validation.

If any admission controller in the chain rejects the request, Kubernetes halts further processing and returns an error message to the user.

![The image illustrates a diagram of admission controllers, highlighting mutating and validating processes with examples like AlwaysPullImages and NamespaceAutoProvision.](https://kodekloud.com/kk-media/image/upload/v1752871295/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Validating-and-Mutating-Admission-Controllers/frame_140.jpg align="left")

## External Admission Controllers: Webhooks

Kubernetes extends its built-in admission controllers with external admission controllers through webhooks. There are two primary webhook types:

* **Mutating Admission Webhook**
    
* **Validating Admission Webhook**
    

These webhooks allow you to deploy a server—either inside or outside your Kubernetes cluster—that implements custom logic for modifying or validating requests. After a request clears all built-in controllers, it is sent as a JSON-formatted admission review object to the webhook server. The server responds with an admission review object indicating whether the request is allowed and, if applicable, provides JSON patches to mutate the request.

For example, a validating webhook admission review request may look like:

```plaintext
{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {
    "uid": "705ab4f5-6393-11e8-b7cc-42010aa80002",
    "kind": { "group": "autoscaling", "version": "v1", "kind": "Scale" },
    "resource": { "group": "apps", "version": "v1", "resource": "deployments" },
    "subResource": "scale",
    "requestKind": { "group": "autoscaling", "version": "v1", "kind": "Scale" },
    "requestResource": { "group": "apps", "version": "v1", "resource": "deployments" }
  }
}
```

And the corresponding webhook response approving the request would be:

```plaintext
{
  "apiVersion": "admission.k8s.io/v1",
  "kind": "AdmissionReview",
  "request": {
    "uid": "705ab4f5-6393-11e8-b7cc-42010aa80002",
    "kind": {"group": "autoscaling", "version": "v1", "kind": "Scale"},
    "resource": {"group": "apps", "version": "v1", "resource": "deployments"},
    "subResource": "scale",
    "requestKind": {"group": "autoscaling", "version": "v1", "kind": "Scale"},
    "requestResource": {"group": "apps", "version": "v1", "resource": "deployments"}
  },
  "response": {
    "uid": "<value_from request.uid>",
    "allowed": true
  }
}
```

## Deploying an Admission Webhook Server

To set up your own admission webhook, follow these general steps:

1. Deploy your webhook server containing custom logic.
    
2. Configure Kubernetes to integrate with your webhook by creating the appropriate webhook configuration object.
    

### Deploying the Webhook Server

You can implement a webhook server in any programming language as long as it supports the mutate and validate APIs and returns the correct JSON response. Below is a simplified example of a webhook server written in Go.

```plaintext
package main


import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"


	"k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/klog"
)


// toAdmissionResponse is a helper function to create an AdmissionResponse with an embedded error.
func toAdmissionResponse(err error) v1beta1.AdmissionResponse {
	return v1beta1.AdmissionResponse{
		Result: &metav1.Status{
			Message: err.Error(),
		},
	}
}


// admitFunc defines the function type for validators and mutators.
type admitFunc func(v1beta1.AdmissionReview) v1beta1.AdmissionResponse


// serve handles the HTTP part of a request before passing it to the admit function.
func serve(w http.ResponseWriter, r *http.Request, admit admitFunc) {
	var body []byte
	if r.Body != nil {
		data, err := ioutil.ReadAll(r.Body)
		if err == nil {
			body = data
		}
	}
	// Further processing of the admission review would occur here.
}
```

You can implement a similar server in other languages. Below is pseudocode for a sample webhook server in Python that handles both validation and mutation:

```plaintext
from flask import Flask, request, jsonify
import base64
import json


app = Flask(__name__)


@app.route("/validate", methods=["POST"])
def validate():
    object_name = request.json["object"]["metadata"]["name"]
    user_name = request.json["request"]["userInfo"]["name"]
    allowed = True
    message = ""
    if object_name == user_name:
        message = "You can't create objects with your own name."
        allowed = False
    return jsonify({
        "response": {
            "allowed": allowed,
            "uid": request.json["request"]["uid"],
            "status": {"message": message},
        }
    })


@app.route("/mutate", methods=["POST"])
def mutate():
    user_name = request.json["request"]["userInfo"]["name"]
    patch = [{"op": "add", "path": "/metadata/labels/users", "value": user_name}]
    # The patch must be base64 encoded before sending in the response.
    encoded_patch = base64.b64encode(json.dumps(patch).encode()).decode()
    return jsonify({
        "response": {
            "allowed": True,
            "uid": request.json["request"]["uid"],
            "patch": encoded_patch,
            "patchType": "JSONPatch",
        }
    })


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=443, ssl_context=("path/to/tls.crt", "path/to/tls.key"))
```

**<mark>Deployment Note</mark>**

In an exam or production environment, you are not expected to write the full webhook implementation code. The focus should be on understanding how admission webhook servers function and how they integrate with Kubernetes.

## Hosting and Configuring the Webhook

Once your webhook server is ready, you can host it as a standalone server or containerize it and deploy it as a Deployment within your Kubernetes cluster. If you opt to deploy within the cluster, remember to create a corresponding Service so that the API server can communicate with your webhook.

Next, configure your Kubernetes cluster by creating a webhook configuration object. For example, to direct pod creation requests to your validating webhook service, you could use the following configuration:

```plaintext
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: "pod-policy.example.com"
webhooks:
- name: "pod-policy.example.com"
  clientConfig:
    service:
      namespace: "webhook-namespace"
      name: "webhook-service"
    caBundle: "Ci0tLS0tQk.....tLS0K"
  rules:
  - apiGroups: [""]
    apiVersions: ["v1"]
    operations: ["CREATE"]
    resources: ["pods"]
    scope: "Namespaced"
```

For a mutating webhook, create a MutatingWebhookConfiguration object instead. In both cases, ensure that TLS is properly configured between the API server and your webhook server by generating the required certificates and providing the CA bundle in the configuration.

With this configuration, every pod creation request triggers a call to your webhook service. Based on the response, the request will either be allowed or rejected.

![The image illustrates deploying an Admission Webhook Server within a Kubernetes cluster, labeled as "webhook-deployment."](https://kodekloud.com/kk-media/image/upload/v1752871297/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Validating-and-Mutating-Admission-Controllers/frame_460.jpg align="left")

That concludes this lesson on Validating and Mutating Admission Controllers. Practice these concepts with hands-on exercises, and stay tuned for more advanced topics in the upcoming lessons.

# Solution Validating and Mutating Admission Controllers

In this lesson, we walk through the lab on validating and mutating admission controllers. You'll gain hands-on experience with namespace creation, TLS secret management, deploying webhook servers, and testing pod security contexts.

![KodeKloud practice test interface showing a terminal and a multiple-choice question about mutating and validating admission controllers.](https://kodekloud.com/kk-media/image/upload/v1752871293/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Solution-Validating-and-Mutating-Admission-Controllers/frame_0.jpg align="left")

The lab begins with a multiple-choice question asking which combination is correct for mutating and validating admission controllers. The key observation is that the namespace auto-provision admission controller performs a mutation by automatically creating or altering a namespace. On the other hand, the namespace existence check is strictly a validation step. Therefore, the correct combination is to treat namespace auto-provisioning as mutating and namespace existence checking as validating.

![KodeKloud practice test interface showing a terminal and a question regarding the flow of invocation of admission controllers.](https://kodekloud.com/kk-media/image/upload/v1752871294/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Solution-Validating-and-Mutating-Admission-Controllers/frame_50.jpg align="left")

## Step 1: Create the Namespace

First, create a namespace named "webhook-demo" to serve as the environment for the webhook operations.

```plaintext
kubectl create ns webhook-demo
```

After executing the command, verify that the namespace is created by listing all namespaces:

```plaintext
kubectl get ns
```

You should see the "webhook-demo" namespace included in the output.

## Step 2: Create a TLS Secret

For secure webhook communication, create a TLS secret named "webhook-server-tls" in the "webhook-demo" namespace. Ensure you substitute the correct file paths for the certificate and key:

```plaintext
kubectl -n webhook-demo create secret tls webhook-server-tls \
  --cert "/root/keys/webhook-server-tls.crt" \
  --key "/root/keys/webhook-server-tls.key"
```

A successful creation message will confirm that the TLS secret has been established.

## Step 3: Deploy the Webhook Server

Review the webhook server deployment definition provided in the file `webhook-deployment.yaml`:

```plaintext
cat webhook-deployment.yaml
```

Deploy the webhook server with the following command:

```plaintext
kubectl apply -f webhook-deployment.yaml
```

## Step 4: Create the Webhook Service

Create a service for the webhook server using the configuration in `webhook-service.yaml`:

```plaintext
kubectl apply -f webhook-service.yaml
```

## Step 5: Apply the Mutating Webhook Configuration

Next, apply the mutating webhook configuration defined in `webhook-configuration.yaml`. This file includes rules under the "rules" section to intercept "CREATE" operations for pods.

```plaintext
kubectl apply -f webhook-configuration.yaml
```

**<mark>Deprecation Notice</mark>**

When applying the webhook configuration, you might receive a deprecation warning indicating that [admissionregistration.k8s.io/v1beta1](http://admissionregistration.k8s.io/v1beta1) is deprecated in favor of v1. Despite the warning, the configuration will still perform its intended function by denying pod creation requests that attempt to run as root when no security context is provided.

In our lab, if no explicit value is given for "runAsNonRoot", a default value of `true` is applied, and the user ID is defaulted to `1234` unless overridden.

## Step 6: Test the Webhook by Deploying Pods

### Pod with Default Security Context

The next stage involves deploying a pod that does not specify any security context so that the webhook can mutate the configuration. The YAML file `pod-with-defaults.yaml` contains the following configuration:

```plaintext
# A pod with no securityContext specified.
# Without the webhook, it would run as user root (0). The webhook mutates it
# to run as the non-root user with uid 1234.
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-defaults
  labels:
    app: pod-with-defaults
spec:
  restartPolicy: OnFailure
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "echo I am running as user $(id -u)"]
```

Deploy the pod with:

```plaintext
kubectl apply -f pod-with-defaults.yaml
```

After deployment, confirm that the pod has been mutated by retrieving the pod details:

```plaintext
kubectl get pod pod-with-defaults -o yaml
```

The webhook should have added a mutated security context, setting `runAsNonRoot: true` and `runAsUser: 1234`.

### Pod with Explicit Override

Next, deploy a pod that explicitly sets its security context to allow running as root. Check the `pod-with-override.yaml` file where the security context is defined with `runAsNonRoot` set to `false`:

```plaintext
kubectl apply -f pod-with-override.yaml
```

Review the file to confirm that the override is applied as intended.

### Pod with Conflicting Security Context

Finally, deploy a pod with a conflicting security configuration in the `pod-with-conflict.yaml` file. This configuration attempts to set `runAsNonRoot: true` while also specifying `runAsUser: 0`, creating a conflict.

```plaintext
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-conflict
  labels:
    app: pod-with-conflict
spec:
  restartPolicy: OnFailure
  securityContext:
    runAsNonRoot: true
  containers:
    - name: busybox
      image: busybox
      command: ["sh", "-c", "echo I am running as user $(id -u)"]
```

Deploy the conflicting pod with:

```plaintext
kubectl apply -f pod-with-conflict.yaml
```

**<mark>Validation Rejection</mark>**

The admission webhook should reject the conflicting pod creation request, displaying an error message similar to the following:

Error from server: error when creating "pod-with-conflict.yaml": admission webhook "webhook-server.webhook-demo.svc" denied the request: runAsNonRoot specified, but runAsUser set to 0 (the root user)

This rejection confirms that the webhook validation is functioning as expected by preventing pods from running as root when it is not allowed.

# API Versions

In this lesson, we explore API versions in Kubernetes—a topic that builds upon core concepts such as APIs, API groups, resources, and verbs. Understanding the evolution of these API versions—from experimental to stable—is key to effectively managing your Kubernetes configurations.

## Overview of API Organization

Kubernetes organizes everything under its API into API groups (e.g., apps, extensions, networking). Each API group can support multiple versions. A version labeled as "v1" typically indicates a generally available (GA) and stable release. Other labels like v1beta1 or v1alpha1 denote beta or alpha stages respectively. Let’s break down what each of these stages means.

## API Version Stages

### Alpha

An alpha version represents the initial development stage of an API. Once an API is added to the Kubernetes code base and included in a release for the first time, it is marked as alpha (for example, v1alpha1, v1alpha2). At this stage, the API is not enabled by default, may lack comprehensive end-to-end tests, and could contain bugs. For instance, at the time of recording, the API group [`internal.apiserver.k8s.io`](http://internal.apiserver.k8s.io) (which includes the StorageVersion resource) exists only in its alpha form.

If you attempt to create an object using the following YAML:

```plaintext
apiVersion: internal.apiserver.k8s.io/v1alpha1
kind: StorageVersion
metadata:
  name: sv-1
spec:
```

the API server will reject the creation because the alpha version is not enabled by default. This version is intended for expert users who want to test and provide early feedback.

### Beta

After addressing major bugs in the alpha API and adding comprehensive tests, the API advances to beta (e.g., v1beta1, v1beta2). Beta APIs are enabled by default and include end-to-end tests. While minor bugs might still exist, there is a commitment from the community to advance these APIs to GA. For example, the flow-control group is currently in the beta stage.

### GA (Stable)

An API promoted to GA has successfully navigated the beta phase, undergone multiple releases, and received numerous bug fixes. The version number drops any alpha or beta suffix, appearing simply as "v1." GA APIs are reliably enabled by default and become part of conformance tests. Most API groups, such as apps, authentication, authorization, certificates, and coordination, are now available in their GA versions.

![The image outlines API versioning stages: Alpha, Beta, and GA (Stable), detailing their version names, enablement, testing, reliability, support, and target audience.](https://kodekloud.com/kk-media/image/upload/v1752871272/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Versions/frame_210.jpg align="left")

**<mark>Note</mark>**

For production environments, always ensure you use GA versions of the APIs to maintain a stable and supported deployment.

## Supporting Multiple Versions in an API Group

An API group can support several versions simultaneously. For example, the apps group might offer v1, v1beta1, and v1alpha1, allowing you to reference any of these versions in your YAML file. Below are examples of a Deployment resource defined using different API versions:

```plaintext
# Example using apps/v1alpha1:
apiVersion: apps/v1alpha1
kind: Deployment
metadata:
  name: nginx
spec:
  # Deployment spec fields go here
---
# Example using apps/v1beta1:
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  # Deployment spec fields go here
---
# Example using apps/v1 (the preferred and storage version):
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  # Deployment spec fields go here
```

While you can create an object with any supported API version, only one version is designated as the *preferred* version. This is the version used by default with commands like `kubectl get deployment` or `kubectl explain deployment`. Furthermore, only one version—the *storage version*—is used to persist objects in etcd. If you create an object using a non-preferred version (like apps/v1alpha1 or apps/v1beta1), Kubernetes will automatically convert it to the storage version (typically apps/v1) before saving.

For example, after creating a Deployment using any API version, running these commands will show that it is stored as apps/v1:

```plaintext
kubectl get deployment
```

```plaintext
kubectl explain deployment
```

```plaintext
KIND:     Deployment
VERSION:  apps/v1
```

## Determining the Preferred and Storage Versions

When multiple API versions are available within a group, the preferred version is listed in the API group details. For example, querying the batch API group might return:

```plaintext
{
  "kind": "APIGroup",
  "apiVersion": "v1",
  "name": "batch",
  "versions": [
    {
      "groupVersion": "batch/v1",
      "version": "v1"
    },
    {
      "groupVersion": "batch/v1beta1",
      "version": "v1beta1"
    }
  ],
  "preferredVersion": {
    "groupVersion": "batch/v1",
    "version": "v1"
  }
}
```

While you can determine the preferred version via the API, the storage version remains hidden. To reveal the storage version, you can query the etcd database directly. For instance, using the etcdctl utility with the following command:

```plaintext
ETCDCTL_API=3 etcdctl \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  get "/registry/deployments/default/blue" --print-value-only
```

You might receive output similar to:

```plaintext
apps/v1
Deployment


bluedefault"*${cf8dcd55-8819-4be2-85e7-bb71665c2ddf2ZB
successfully progressed8"2
```

This output confirms that the object is stored as apps/v1.

## Enabling or Disabling Specific API Versions

To modify which API versions are enabled, adjust the `--runtime-config` parameter of the kube-apiserver. For instance, if you need to enable alpha APIs that aren’t enabled by default, update the kube-apiserver's service configuration. Below is an example ExecStart configuration for kube-apiserver:

```plaintext
ExecStart=/usr/local/bin/kube-apiserver \
  --advertise-address=${INTERNAL_IP} \
  --allow-privileged=true \
  --apiserver-count=3 \
  --authorization-mode=Node,RBAC \
  --bind-address=0.0.0.0 \
  --enable-swagger-ui=true \
  --etcd-cafile=/var/lib/kubernetes/ca.pem \
  --etcd-certfile=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --etcd-keyfile=/var/lib/kubernetes/apiserver-etcd-client.key \
  --etcd-servers=https://127.0.0.1:2379 \
  --event-ttl=1h \
  --kubelet-certificate-authority=/var/lib/kubernetes/ca.pem \
  --kubelet-client-certificate=/var/lib/kubernetes/apiserver-etcd-client.crt \
  --kubelet-client-key=/var/lib/kubernetes/apiserver-etcd-client.key \
  --kubelet-https=true \
  --runtime-config=api/all \
  --service-account-key-file=/var/lib/kubernetes/service-account.pem \
  --service-cluster-ip-range=10.32.0.0/24 \
  --service-node-port-range=30000-32767 \
  --client-ca-file=/var/lib/kubernetes/ca.pem \
  --tls-cert-file=/var/lib/kubernetes/apiserver.crt \
  --tls-private-key-file=/var/lib/kubernetes/apiserver.key \
  --v=2
```

After updating the configuration to enable the desired API versions (specified as comma-separated values in the `--runtime-config` parameter), restart the kube-apiserver service for the changes to take effect.

**<mark>Warning</mark>**

Always back up your configuration and validate your changes in a non-production environment before applying them to production.

## Conclusion

This lesson provided an in-depth look at Kubernetes API versions, the significance of each version stage (Alpha, Beta, and GA), and how Kubernetes handles multiple API versions within a single API group. By understanding preferred and storage versions along with how to enable or disable specific API versions, you are better equipped to manage your Kubernetes infrastructure effectively.

# API Deprecations

In this lesson, we explore API deprecations and their impact on managing API lifecycles in Kubernetes. We explain how a single API group can support multiple versions, why multiple versions are necessary, how many versions should be supported, and when older versions can be removed. The API deprecation policy provides guidelines for efficient API lifecycle management.

---

## API Group Lifecycle Example

Imagine contributing to the Kubernetes project by creating an API group named `kodekloud.com` that contains two resources: `Course` and `Webinar`. Initially, both resources are developed and tested in-house. Once ready for integration, a pull request is created, and the API group is released as an alpha version (specifically, `v1alpha1`). For example, a YAML file to create a `Course` might look like:

```plaintext
apiVersion: kodekloud.com/v1alpha1
kind: Course
metadata:
  name: ckad
spec:
```

If user feedback suggests that the `Webinar` resource is no longer needed, the first rule of the API deprecation policy states that API elements can only be removed by incrementing the API group's version. Therefore, the `Webinar` resource is removed in the subsequent version (v1alpha2), while v1alpha1 remains available. The `Course` definition in the two versions is as follows:

```plaintext
apiVersion: kodekloud.com/v1alpha1
kind: Course
metadata:
  name: ckad
spec:
```

```plaintext
apiVersion: kodekloud.com/v1alpha2
kind: Course
metadata:
  name: ckad
spec:
```

Even though the in-memory resource remains stored as v1alpha1, the current API version gets updated to v1alpha2. Both versions need to be supported until the change is fully adopted. Eventually, one of these versions (typically v1alpha2) becomes the preferred or storage version—ensuring that while users may continue to use v1alpha1 YAML files, the resource is stored internally as v1alpha2.

---

## Round-Trip Conversion Between API Versions

The second rule of the API deprecation policy mandates that API objects must support round-trip conversion between versions without any loss of information (except for complete REST resources that do not exist in all versions). For instance, consider a `Course` object created in v1alpha1 with a `spec` field:

```plaintext
apiVersion: kodekloud.com/v1alpha1
kind: Course
metadata:
  name: ckad
spec:
  type: video
```

In v1alpha2, a new field named `duration` is introduced. The conversion to v1alpha2 could be represented as:

```plaintext
apiVersion: kodekloud.com/v1alpha2
kind: Course
metadata:
  name: ckad
spec:
  type: video
  duration:
```

To ensure fidelity when converting back to v1alpha1, an equivalent field for `duration` must be provided in v1alpha1—even if only as a placeholder—so that the round-trip conversion retains its integrity.

---

## Evolving Through Beta to GA

After addressing bugs and refining the API in the alpha stages, the next phase is beta. For example, after releasing the first beta version (`v1beta1`), further refinements result in `v1beta2`, and finally, the stable GA version (`v1`) is released. The typical evolution follows this sequence:

1. v1alpha1 → v1alpha2 (with potentially additional alpha versions)
    
2. v1beta1 and v1beta2 (both beta versions are supported for a defined period)
    
3. v1 GA (stable release)
    

The API deprecation policy requires continued support for older versions as follows:

* GA: At least 12 months or three releases.
    
* Beta: At least nine months or three releases.
    
* Alpha: No support beyond the current release.
    

For example, consider a Kubernetes release sequence:

* In release x, `v1alpha1` is introduced.
    
* In release x+1, `v1alpha2` is introduced. Since alpha versions do not have extended support beyond the next release, `v1alpha1` is dropped.
    

![The image illustrates API group versions and Kubernetes release versions, showing preferred/storage versions as "/v1alpha1" and "/v1alpha2" with corresponding release versions "X" and "X+1".](https://kodekloud.com/kk-media/image/upload/v1752871261/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Deprecations/frame_330.jpg align="left")

According to the deprecation policy (rule four), older API versions must be supported after deprecation for a mandated period. In release x+1, if `v1alpha1` is removed, this change must be clearly documented in the release notes. Future updates may also include the removal of deprecated APIs, such as v1alpha2, urging users to migrate to newer versions.

In release x+2, the first beta version (`v1beta1`) appears. Over subsequent releases (x+3, x+4, etc.), both the current and previous beta versions are supported for compatibility. During this overlap:

* The older beta version (e.g., `v1beta1`) is deprecated but is not removed immediately.
    
* Users receive deprecation warnings prompting migration to the newer beta version.
    
* The preferred API version (and storage version) remains unchanged until the next release when only the newer version is supported.
    

![The image illustrates the relationship between API group versions and Kubernetes release versions, showing version progression and deprecation status.](https://kodekloud.com/kk-media/image/upload/v1752871263/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Deprecations/frame_470.jpg align="left")

When the GA version (v1) is released (e.g., in release x+5), beta versions (`v1beta1` and `v1beta2`) remain available temporarily until they satisfy the support duration requirements. In release x+6, `v1beta1` may be removed after supporting it for three releases, while `v1beta2` continues to be supported until it completes its deprecation period and is eventually dropped (e.g., in release x+8).

![The image illustrates the relationship between API group versions and Kubernetes release versions, highlighting deprecated versions and preferred storage versions.](https://kodekloud.com/kk-media/image/upload/v1752871264/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Deprecations/frame_590.jpg align="left")

---

## Introducing a New API Version (v2)

When a new major API version such as `v2alpha1` is introduced, the previous stable version (v1, GA) is not immediately deprecated. The third rule of the API deprecation policy specifies that an API version must not be deprecated until an equivalent or more stable version is available. Since `v2alpha1` is an alpha release and v1 is in GA, v1 continues to be supported. The new version will then progress through its lifecycle—v2alpha2, v2beta1, v2beta2, and finally v2—before it can deprecate v1.

![The image shows a chart comparing API group versions with Kubernetes release versions, indicating deprecated versions and preferred storage versions.](https://kodekloud.com/kk-media/image/upload/v1752871265/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-API-Deprecations/frame_640.jpg align="left")

---

## Using kubectl convert

As Kubernetes evolves and older API versions are removed, manifest files must be updated to the current API versions. For example, if you have a YAML file with a deployment definition using `v1beta1` and your Kubernetes upgrade removes that version, you must update your manifest to use `apps/v1`:

```plaintext
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
```

For projects with numerous YAML files requiring conversion, the `kubectl convert` command is an efficient tool. To convert your deployment file to `apps/v1`, run:

```plaintext
kubectl convert -f nginx.yaml --output-version apps/v1
```

The converted YAML manifest will be displayed on your screen.

!!! note "Note" The `kubectl convert` command might not be installed by default as it is available as a separate plugin. Follow these steps to install it:

```plaintext
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl-convert"
```

```plaintext
curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl-convert.sha256"
```

```plaintext
echo "$(<kubectl-convert.sha256) kubectl-convert" | sha256sum --check
```

For further details, please refer to the [**Kubernetes documentation**](https://kubernetes.io/docs/). These instructions, along with hands-on labs, will help you become proficient with the `kubectl convert` plugin.

# Solution API VersionsDeprecations

In this lesson, you'll learn how to address API versioning and deprecation issues in Kubernetes. We'll cover how to find resource short names, determine API groups, discover preferred API versions, and enable deprecated API endpoints. Follow along with the steps and code blocks below.

## Short Names for Resources

First, identify the short names for key resources such as Deployments, ReplicaSets, CronJobs, and Custom Resource Definitions (CRDs). Run the following command:

```plaintext
kubectl api-resources
```

Review the output to locate the following mappings:

* Deployments: **deploy**
    
* ReplicaSets: **rs**
    
* CronJobs: **cj**
    
* Custom Resource Definitions: **crd** (or **crds**)
    

An excerpt from the command output may look like this:

```plaintext
deployments                deploy
replicasets                rs
cronjobs                   cj
customresourcedefinitions  crd, crds
```

**<mark>Quick Tip</mark>**

Use the output of `kubectl api-resources` to quickly reference the abbreviated names in your commands.

## Determining the API Group for a Resource

To determine which API group a resource belongs to—for example, the Job resource—execute:

```plaintext
kubectl explain job
```

At the top of the output, you'll find details similar to:

```plaintext
KIND: Job
VERSION: batch/v1
```

This indicates that the Job belongs to the **batch** API group (the section before the slash) and is using version **v1**.

## Finding the Preferred Version for a Specific API Group

Next, find the preferred version for the [`authorization.k8s.io`](http://authorization.k8s.io) API group by following these steps:

1. **Start a Local API Proxy:**  
    Run the following command to create a local proxy to the Kubernetes API server:
    
    ```plaintext
    kubectl proxy --port=8001 &
    ```
    
2. **Query the API Group Details:**  
    With the proxy running in the background, use `curl` to fetch the API group's details:
    
    ```plaintext
    curl localhost:8001/apis/authorization.k8s.io
    ```
    

The JSON response will include a section for the preferred version:

```plaintext
{
   "kind": "APIGroup",
   "apiVersion": "v1",
   "name": "authorization.k8s.io",
   "versions": [
      {
         "groupVersion": "authorization.k8s.io/v1",
         "version": "v1"
      }
   ],
   "preferredVersion": {
      "groupVersion": "authorization.k8s.io/v1",
      "version": "v1"
   }
}
```

This confirms that the preferred version for [`authorization.k8s.io`](http://authorization.k8s.io) is **v1**.

## Enabling the v1alpha1 Version for [RBAC.authorization.k8s.io](http://RBAC.authorization.k8s.io)

To enable the `v1alpha1` version for the [`RBAC.authorization.k8s.io`](http://RBAC.authorization.k8s.io) API group on the control plane node, perform the following steps:

1. **Backup the Current Manifest:**  
    The kube-apiserver manifest is located at `/etc/kubernetes/manifests/kube-apiserver.yaml`. Back it up with:
    
    ```plaintext
    cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.backup
    ```
    
2. **Modify the Manifest:**  
    Open the manifest file in your preferred text editor. Scroll down to the section containing command-line arguments and add the following flag at the bottom of the list:
    
    ```plaintext
    --runtime-config=rbac.authorization.k8s.io/v1alpha1
    ```
    
    Below is an excerpt from the modified section:
    
    ```plaintext
    --etcd-key-file=/etc/kubernetes/pki/apiserver-etcd-client.key
    --etcd-servers=https://127.0.0.1:2379
    --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt
    --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key
    --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname
    --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt
    --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key
    --requestheader-allowed-names=front-proxy-client
    --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt
    --requestheader-extra-headers-prefix=X-Remote-Extra-
    --requestheader-group-headers=X-Remote-Group
    --requestheader-username-headers=X-Remote-User
    --secure-port=6443
    --service-account-issuer=https://kubernetes.default.svc.cluster.local
    --service-account-signing-key-file=/etc/kubernetes/pki/sa.pub
    --service-cluster-ip-range=10.96.0.0/12
    --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
    --runtime-config=rbac.authorization.k8s.io/v1alpha1
    image: k8s.gcr.io/kube-apiserver:v1.23.0
    imagePullPolicy: IfNotPresent
    livenessProbe:
      failureThreshold: 8
      httpGet:
        host: 10.69.230.9
        path: /livez
        port: 6443
        scheme: HTTPS
      initialDelaySeconds: 10
    ```
    
3. **Verify the Update:**  
    After saving the changes, the kube-apiserver pod will automatically restart. Confirm its status by running:
    
    ```plaintext
    kubectl get pod -n kube-system
    ```
    
    Initially, the API server pod might display as **Pending**, but it should soon change to **Running**.
    

**<mark>Caution</mark>**

Always back up your kube-apiserver manifest before making any modifications to ensure you can revert changes if needed.

## Installing the kubectl-convert Plugin

The kubectl-convert plugin is a versatile tool to convert manifest files between different API versions. Follow these steps to install it on the control plane node:

1. **Download the Plugin:**  
    Retrieve the binary using the commands below:
    
    ```plaintext
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl-convert"
    curl -LO "https://dl.k8s.io/release/stable.txt" | sha256sum --check
    ```
    
2. **Make the Binary Executable and Move It:**  
    Change its permissions and move it to `/usr/local/bin` with these commands:
    
    ```plaintext
    chmod +x kubectl-convert
    mv kubectl-convert /usr/local/bin/
    ```
    
3. **Verify the Installation:**  
    Execute the help command to ensure the plugin is installed correctly:
    
    ```plaintext
    kubectl convert --help
    ```
    

The help output confirms the successful installation of the kubectl-convert plugin.

## Converting an Ingress Manifest

Finally, update an existing Ingress manifest from the deprecated API version (`v1beta1`) to the current [`networking.k8s.io/v1`](http://networking.k8s.io/v1). Follow these steps:

1. **Convert the Manifest:**  
    The old ingress manifest is located at `/root/ingress-old.yaml`. Convert the API version using the following command:
    
    ```plaintext
    kubectl convert -f ingress-old.yaml --output-version networking.k8s.io/v1 > ingress-new.yaml
    ```
    
    This command creates a new file named `ingress-new.yaml` with the updated API version.
    
2. **Apply the New Manifest:**  
    Deploy the updated Ingress configuration by running:
    
    ```plaintext
    kubectl apply -f ingress-new.yaml
    ```
    

If successful, you should see an output similar to:

```plaintext
ingress.networking.k8s.io/ingress-space created
```

# Custom Resource Definition

In this lesson, we explore how Custom Resource Definitions (CRDs) work in Kubernetes. You will learn how standard Kubernetes resources, such as Deployments, are created, stored in etcd, and managed by built-in controllers. Then, we’ll demonstrate how to extend Kubernetes by defining and using a custom resource—illustrated here as a "FlightTicket"—and explain why a dedicated custom controller is necessary to act upon these new resources.

---

## Standard Kubernetes Resource: Deployment

When you create a Deployment in Kubernetes, the API server stores its state in etcd. A built-in controller, known as the deployment controller, continuously monitors the Deployment to ensure that the desired state (for example, maintaining three replicas) is met by creating or deleting pods as needed.

Below is an example Deployment definition file:

```plaintext
# deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      type: front-end
  template:
    metadata:
      name: myapp-pod
      labels:
        type: front-end
    spec:
      containers:
      - image: nginx
```

Using the file above, you can create, query, and delete the deployment with the following commands:

```plaintext
kubectl create -f deployment.yml
# Output:
kubectl get deployments
# Output:
# NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
kubectl delete -f deployment.yml
# Output:
# deployment "myapp-deployment" deleted
```

The deployment controller automatically creates a ReplicaSet, which manages the specified number of pods. This automation lies at the heart of Kubernetes resource management.

---

## Custom Resource: FlightTicket

Imagine you want to manage something entirely new on your cluster—such as booking a flight ticket. In this example, we define a custom resource called FlightTicket that represents a flight ticket booking. The resource encapsulates details such as the departure and destination airports and the number of tickets required.

Below is an example of a FlightTicket resource file:

```plaintext
# flightticket.yml
apiVersion: flights.com/v1
kind: FlightTicket
metadata:
  name: my-flight-ticket
spec:
  from: Mumbai
  to: London
  number: 2
```

When you create this custom resource, you expect that:

* It is stored in etcd.
    
* A custom controller (which you will build) watches for create, update, or delete events.
    
* The controller automatically makes the necessary API calls (for example, to an external flight booking API) to either book or cancel the ticket.
    

**<mark>Important</mark>**

Attempting to create the FlightTicket resource without first informing Kubernetes of its existence will result in an error:

```plaintext
kubectl create -f flightticket.yml
# Output:
# no matches for kind "FlightTicket" in version "flights.com/v1"
```

This error means that Kubernetes does not yet recognize the FlightTicket type.

---

## Defining a Custom Resource Definition (CRD)

To allow the Kubernetes API to accept FlightTicket objects, you must create a CRD that informs the API server about this new resource type. The CRD includes details such as API version, kind, metadata, spec, and schema information (including supported fields, types, and validation rules).

Here’s an example of a CRD for the FlightTicket resource:

```plaintext
# flightticket-custom-definition.yml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: flighttickets.flights.com
spec:
  group: flights.com
  scope: Namespaced
  names:
    plural: flighttickets
    singular: flightticket
    kind: FlightTicket
    shortNames:
      - ft
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                from:
                  type: string
                to:
                  type: string
                number:
                  type: integer
                  minimum: 1
```

Key points in the CRD:

* The API group is [`flights.com`](http://flights.com).
    
* The resource scope is namespace-scoped.
    
* Both singular and plural names are defined along with a short name (`ft`).
    
* Only one version (`v1`) is served and defined as the storage version.
    
* An OpenAPI v3 schema enforces that the `spec` includes `from` and `to` as strings, and `number` as an integer with a minimum value of 1.
    

After creating the CRD, you can create, retrieve, and delete FlightTicket resources using these commands:

```plaintext
kubectl create -f flightticket-custom-definition.yml
# Output:
kubectl create -f flightticket.yml
# Output:
kubectl get flightticket
# Output:
# NAME             STATUS
kubectl delete -f flightticket.yml
# Output:
# flightticket "my-flight-ticket" deleted
```

To verify that your new resource is recognized by Kubernetes, run:

```plaintext
kubectl api-resources
# Output snippet:
# NAME            SHORTNAMES   APIGROUP     NAMESPACED   KIND
# flighttickets   ft           flights.com  true         FlightTicket
```

---

## The Role of a Custom Controller

While defining a CRD enables Kubernetes to store and retrieve FlightTicket objects in etcd, these objects remain inactive without a controller. A custom controller, often implemented in Go, observes events related to FlightTicket resources and executes business logic (such as calling external APIs to book or cancel flights).

Below is a simplified snippet of what such a controller might look like:

```plaintext
package flightticket


var controllerKind = apps.SchemeGroupVersion.WithKind("FlightTicket")


// Run begins watching and syncing FlightTicket resources.
func (dc *FlightTicketController) Run(workers int, stopCh <-chan struct{}) {
    // Controller logic goes here
}


// callBookFlightAPI handles the API call to book a flight ticket.
func (dc *FlightTicketController) callBookFlightAPI(obj interface{}) {
    // API call implementation goes here
}
```

**<mark>Note</mark>**

Without a custom controller, any FlightTicket resource you create remains a passive data record in etcd without triggering any external actions.

# Custom Controllers

In this lesson, we explore how to develop custom controllers for managing your Kubernetes resources. Building on our previous work with Custom Resource Definitions (CRDs), we now introduce FlightTicket objects. These objects, along with their details, are stored in etcd. The custom controller monitors the status of these FlightTicket objects in etcd and performs actions such as booking, editing, or canceling flight tickets by invoking the appropriate flight booking API.

A controller is a process or piece of code that continuously observes the Kubernetes cluster for specific events (like changes to FlightTicket objects) and takes corresponding actions.

For example, consider the following YAML definition of a FlightTicket resource:

```plaintext
# flightticket.yml
apiVersion: flights.com/v1
kind: FlightTicket
metadata:
  name: my-flight-ticket
spec:
  from: Mumbai
  to: London
  number: 2
```

You can create this resource and verify its status using these commands:

```plaintext
kubectl create -f flightticket.yml
# Output:
kubectl get flightticket
# Output:
# NAME              STATUS
# my-flight-ticket  Pending
```

**<mark>Note</mark>**

While you can implement controllers in various programming languages, using the Kubernetes Go client is recommended. The Go client (client-go library) provides shared informers that offer efficient caching and queuing mechanisms, making it ideal for building robust controllers.

The same FlightTicket YAML definition provides context for this process:

```plaintext
# flightticket.yml
apiVersion: flights.com/v1
kind: FlightTicket
metadata:
  name: my-flight-ticket
spec:
  from: Mumbai
  to: London
  number: 2
```

And here are the related creation commands:

```plaintext
kubectl create -f flightticket.yml
# Output:
kubectl get flightticket
# Output:
# NAME              STATUS
# my-flight-ticket  Pending
```

Using Go simplifies the development process due to its seamless integration with Kubernetes libraries that support robust controller patterns.

## Getting Started with a Custom Controller

To build your custom controller, follow these steps:

1. **Clone the SampleController Repository**  
    Clone the repository from GitHub using the following command:
    
    ```plaintext
    git clone https://github.com/kubernetes/sample-controller.git
    # Cloning into 'sample-controller'...
    # Resolving deltas: 100% (15787/15787), done.
    ```
    
2. **Customize Your Controller Logic**  
    Navigate to the repository directory and modify the `controller.go` file to include your custom logic, such as invoking the flight booking API:
    
    ```plaintext
    cd sample-controller
    go build -o sample-controller .
    # Output during build might include:
    # go: downloading k8s.io/client-go v0.0.0-20211001003700-dbfa30b9d908
    # go: downloading golang.org/x/text v0.3.6
    ```
    
3. **Run the Controller**  
    Execute the controller by specifying the `kubeconfig` file for authentication:
    
    ```plaintext
    ./sample-controller --kubeconfig=$HOME/.kube/config
    # Example output:
    # I1013 02:11:07.489479   4017 controller.go:115] Setting up event handlers
    # I1013 02:11:07.489701   4017 controller.go:156] Starting FlightTicket controller
    ```
    
    When executed, the controller runs locally, monitors the creation of FlightTicket objects, and triggers the necessary API calls.
    

**<mark>Deployment Tip</mark>**

After verifying that your controller functions correctly, consider packaging it into a Docker image and deploying it inside your Kubernetes cluster as a pod or deployment. This approach eliminates the need for manual rebuilding and execution each time.

# Operator Framework

In this lesson, we'll explore the operator framework, a powerful method for managing Kubernetes custom resources by combining Custom Resource Definitions (CRDs) and custom controllers into a unified deployment.

Traditionally, you would manually create a CRD along with its corresponding controller (deployed as a Pod or Deployment). The operator framework streamlines this process by packaging both components together. For example, when you deploy a flight operator, it automatically creates the necessary CRD, provisions custom resources, and deploys the custom controller as a Deployment.

Below is an example configuration for a flight ticket CRD and its corresponding controller:

```plaintext
# flightticket-custom-definition.yml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: flighttickets.flights.com
spec:
  scope: Namespaced
  group: flights.com
  names:
    kind: FlightTicket
    singular: flightticket
    plural: flighttickets
    shortnames:
      - ft
  versions:
    - name: v1
      served: true
      storage: true
```

```plaintext
package flightticket


import (
    "k8s.io/api/apps/v1"
)


var controllerKind = v1.SchemeGroupVersion.WithKind("Flightticket")


// Run begins watching and syncing.
func (dc *FlightTicketController) Run(workers int, stopCh <-chan struct{}) {
    // Controller logic goes here.
}


// callBookFlightAPI initiates the flight booking process.
func (dc *FlightTicketController) callBookFlightAPI(obj interface{}) {
    // API call implementation.
}
```

```plaintext
kubectl create -f flight-operator.yaml
```

The operator framework offers extensive automation capabilities beyond simple deployments. A notable real-world example is the etcd operator—which deploys and manages an etcd cluster within Kubernetes. It includes a CRD for defining the etcd cluster and a custom controller that continuously monitors etcd resources. This operator also simplifies advanced operations like backups and restores by enabling these actions with the creation of dedicated CRDs.

The diagram below visualizes an operator framework structure that includes CRDs and custom controllers such as EtcdCluster, EtcdBackup, EtcdRestore, ETCD Controller, and Backup Operator:

![The image illustrates an Operator Framework with Custom Resource Definitions (CRD) and Custom Controllers, including EtcdCluster, EtcdBackup, EtcdRestore, ETCD Controller, and Backup Operator.](https://kodekloud.com/kk-media/image/upload/v1752871289/notes-assets/images/Certified-Kubernetes-Application-Developer-CKAD-Operator-Framework/frame_80.jpg align="left")

**<mark>Note</mark>**

Operators simplify complex processes by bundling CRDs and controllers, making tasks like deployment, backup, and recovery more efficient. However, keep in mind that understanding CRDs remains essential, especially for certification exams.

Deploying an operator typically involves three main steps:

1. Installing the Operator Lifecycle Manager (OLM).
    
2. Installing the operator.
    
3. Verifying the installation.
    

For instance, to deploy the etcd operator, you might use the following commands:

```plaintext
curl -sL https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/install.sh | bash -s v0.19.1
```

```plaintext
kubectl create -f https://operatorhub.io/install/etcd.yaml
```

```plaintext
kubectl get csv -n my-etcd
```

**<mark>Important</mark>**

While operators offer significant advantages in automating deployment and management tasks, ensure that you have a solid understanding of CRDs, as they are a core component of Kubernetes and a primary focus of the exam curriculum.
