Thursday, 5 March 2026

How to install Clickhouse in AWS EKS cluster via Altinity Helm charts and Terraform


 
Installing ClickHouse on an AWS EKS cluster using Terraform and the Altinity Helm charts typically involves two stages:
  1. Installing the Altinity ClickHouse Operator
  2. Deploying a ClickHouse Installation (CHI)
The Altinity Helm repository is located at https://helm.altinity.com.


Prerequisites


Ensure your Terraform environment is configured with the following providers:
  • aws: To manage EKS and underlying infrastructure.
  • kubernetes: To interact with the EKS cluster.
  • helm: To install the operator.

Terraform Configuration


The following example uses the helm_release resource to install the operator and the kubernetes_manifest resource to deploy the actual ClickHouse cluster.

Step A: Install the Altinity Operator


The operator is the "brain" that manages ClickHouse instances on Kubernetes.


resource "helm_release" "clickhouse_operator" {
  name             = "clickhouse-operator"
  repository       = "https://helm.altinity.com"
  chart            = "altinity-clickhouse-operator"
  namespace        = "clickhouse-operator"
  create_namespace = true

  # Optional: Enable metrics for Prometheus
  set {
    name  = "metrics.enabled"
    value = "true"
  }
}


Step B: Deploy a ClickHouse Cluster (CHI)


Once the operator is running, you define your ClickHouse cluster using a Custom Resource (CRD). In Terraform, you use kubernetes_manifest.


resource "kubernetes_manifest" "clickhouse_cluster" {
  depends_on = [helm_release.clickhouse_operator]

  manifest = {
    apiVersion = "clickhouse.altinity.com/v1"
    kind       = "ClickHouseInstallation"
    metadata = {
      name      = "simple-clickhouse"
      namespace = "clickhouse-operator"
    }
    spec = {
      configuration = {
        clusters = [
          {
            name = "cluster1"
            layout = {
              shardsCount   = 1
              replicasCount = 1
            }
          }
        ]
      }
    }
  }
}


Production Considerations for EKS


When running ClickHouse on EKS, you should consider storage and networking:
  • Storage Class: Use AWS gp3 volumes for a good balance of price and performance. You can specify a volumeClaimTemplate in your kubernetes_manifest.
  • Node Affinity: It is recommended to run ClickHouse on specific node groups (e.g., using i3 or r5 instances) to ensure it doesn't compete with other workloads for IOPS.
  • Zookeeper/Keeper: For multi-node shards or replicas, you will need a Zookeeper cluster or the ClickHouse Keeper (also available via Altinity charts).

EKS Module


Altinity maintains a dedicated Terraform EKS ClickHouse module that automates the entire VPC, EKS, and ClickHouse setup if you prefer a pre-packaged solution.


How to view Clickhouse Installation Configuration?



% kubectl get chi -n clickhouse -o yaml                      
apiVersion: v1
items:
- apiVersion: clickhouse.altinity.com/v1
  kind: ClickHouseInstallation
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
 {"apiVersion":"clickhouse.altinity.com/v1","kind":"ClickHouseInstallation","metadata":....}
    creationTimestamp: "2025-01-28T14:35:46Z"
    finalizers:
    - finalizer.clickhouseinstallation.altinity.com
    generation: 12
    name: clickhouse
    namespace: clickhouse
    resourceVersion: "67251031"
    uid: 9fxxxx1-81e7-429b-9cf7-ffxxxxxxef
  spec:
    configuration:
      clusters:
      - layout:
          replicasCount: 1
          shardsCount: 1
        name: ch
        templates:
          dataVolumeClaimTemplate: ch-data
          podTemplate: ch-pod
          serviceTemplate: ch-svc
      users:
        admin/grants/query: GRANT ALL ON *.*
        admin/networks/ip: 0.0.0.0/0
        admin/password: my-admin-password
(or     admin/password_sha256_hex: my-admin-password-in-sha256)
        admin/profile: xxxx
        admin/quota: xxxxx
        admin/settings/enable_http_compression: 1
        default/k8s_secret_password_sha256_hex: <namespace/secretName/key>
        default/profile: default
        default/quota: default
    templates:
      podTemplates:
      - name: ch-pod
        spec:
          containers:
          - image: altinity/clickhouse-server:24.8.14.10544.altinitystable
            name: clickhouse
          - args:
            - server
            env:
            - name: LOG_LEVEL
              value: info
            - name: API_LISTEN
              value: 0.0.0.0:7171
            - name: API_CREATE_INTEGRATION_TABLES
              value: "true"
            - name: REMOTE_STORAGE
              value: s3
            - name: BACKUPS_TO_KEEP_REMOTE
              value: "2"
            - name: S3_BUCKET
              value: my-clickhouse-backups
            - name: S3_REGION
              value: us-east-1
            - name: CLICKHOUSE_HOST
              value: localhost
            - name: CLICKHOUSE_USERNAME
              value: xxxxx
            - name: CLICKHOUSE_PASSWORD
              value: xxxx
            image: altinity/clickhouse-backup:latest
            imagePullPolicy: IfNotPresent
            name: clickhouse-backup
          serviceAccountName: clickhouse-backup
          tolerations:
          - effect: NoSchedule
            key: karpenter/clickhouse
            operator: Exists
      serviceTemplates:
      - metadata:
          annotations:
            service.beta.kubernetes.io/aws-load-balancer-ip-address-type: ipv4
            service.beta.kubernetes.io/aws-load-balancer-name: my-clickhouse-nlb
            service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
            service.beta.kubernetes.io/aws-load-balancer-scheme: internal
            service.beta.kubernetes.io/aws-load-balancer-type: nlb
          name: clickhouse
        name: ch-svc
        spec:
          ports:
          - name: http
            port: 8123
            targetPort: 8123
          - name: native
            port: 9000
            targetPort: 9000
          type: LoadBalancer
      volumeClaimTemplates:
      - name: ch-data
        spec:
          accessModes:
          - ReadWriteOnce
          resources:
            requests:
              storage: 100Gi
  status:
    chop-commit: 9abcd12
    chop-date: 2025-01-24T08:40:12
    chop-ip: 10.x.x.x
    chop-version: 0.25.5
    clusters: 1
    endpoint: clickhouse-clickhouse.clickhouse.svc.cluster.local
    fqdns:
    - chi-clickhouse-ch-0-0.clickhouse.svc.cluster.local
    hosts: 1
    hostsWithTablesCreated:
    - chi-clickhouse-ch-0-0.clickhouse.svc.cluster.local
    pods:
    - chi-clickhouse-ch-0-0-0
    shards: 1
    status: Completed
    taskID: auto-1xxxxd2-5ba4-4c3a-9daa-baxxxxx850
    taskIDsCompleted:
    - auto-1fxxxxxd2-5ba4-4c3a-9daa-baxxxxxx50
      ...
    - auto-bbxxxx6-31e3-4a4c-b04b-e5xxxxxx91
    taskIDsStarted:
    - auto-31xxxxx37-492f-4109-b515-4axxxxxx6c8
      ... 
    - auto-b8xxxxx7-0396-41e0-b5d1-95xxxxd48
kind: List
metadata:
  resourceVersion: ""


Users section shows users config in form USER_NAME/ATTRIBUTE. In the example above we have two users: admin and default.

USER_NAME/password values is plain text password. This is very convenient for debugging (though usually a security "no-no" for production, especially if that's admin or default user!).

USER_NAME/password_sha256_hex is a SHA256 hashed password.

USER_NAME/k8s_secret_password_sha256_hex: <namespace/SECRET_NAME/KEY_NAME > shows that USER_NAME ClickHouse user is secured using a Kubernetes Secret. This maps the default user's password to a specific Kubernetes Secret. 

  • USER_NAME/k8s_secret_password_sha256_hex: Specifies that for the user named USER_NAME, the password should be read from a Kubernetes Secret as a SHA256 hex string.
  • <namespace/SECRET_NAME/KEY_NAME >: This is the reference to the secret itself, structured as namespace/SECRET_NAME/KEY_NAME.
  • Purpose: This allows for secure, GitOps-friendly password management, preventing plain-text passwords from appearing in Kubernetes manifests.
  • Implementation: The ClickHouse Operator reads this secret and places the hashed password into the users.xml file for the ClickHouse server. Operator reads the secret, hashes the password (if necessary), and writes it into a file called /etc/clickhouse-server/users.d/chop-generated-users.xml inside your ClickHouse pod. If you have External Secrets installed, this secret is likely being pulled from AWS Secrets Manager.
  • Alternative: You can also use k8s_secret_env_password_sha256_hex to load the password via an environment variable.

In the Altinity Operator, the syntax USER_NAME/k8s_secret_password_sha256_hex is a pointer. It tells the operator to look into a specific secret to find the password hash for the USER_NAME user.

To get the password:

% kubectl get secret <SECRET_NAME> \
    -n <NAMESPACE> \
    -o jsonpath="{.data.<KEY_NAME>}" \
    | base64 -d

NAMESPACE is usually clickhouse.


How to check Clickhouse health?


Since ClickHouse is running in our cluster, the best way to verify it's "working fine" is to move beyond just checking the Pod status and actually query the database engine itself.

Here is a 3-step approach to verify health, connectivity, and data integrity.

1. The "Internal" Health Check


The quickest way is to execute a command directly inside the pod using the clickhouse-client. This bypasses networking issues and tells you if the engine is responsive. Run this command:

kubectl exec -it chi-clickhouse-ch-0-0 \
-n clickhouse \
-- clickhouse-client --query "SELECT version(), uptime()"

chi-clickhouse-ch-0-0 is the name of the pod, it can also be like chi-clickhouse-ch-0-0-0.

If this returns data, it means ClickHouse is successfully reading from its system tables on the EBS volume.

What to look for: It should return the version string and the number of seconds the server has been up. If this fails, the DB engine itself is hung.

If you are using default user which has a password, or, default user was disabled, the output might show the error similar to this:

% kubectl exec -it chi-clickhouse-ch-0-0-0 -n clickhouse -- clickhouse-client --query "SELECT version(), uptime()"
Defaulted container "clickhouse" out of: clickhouse, clickhouse-backup
Code: 516. DB::Exception: Received from localhost:9000. DB::Exception: default: Authentication failed: password is incorrect, or there is no user with such name.

If you have installed ClickHouse and forgot password you can reset it in the configuration file.
The password for default user is typically located at /etc/clickhouse-server/users.d/default-password.xml
and deleting this file will reset the password.
See also /etc/clickhouse-server/users.xml on the server where ClickHouse is installed.

. (AUTHENTICATION_FAILED)

command terminated with exit code 4

Seeing an AUTHENTICATION_FAILED error instead of a Connection Refused error is actually a positive result for this check:
  • Networking works: Your kubectl exec reached the pod.
  • Process is alive: The ClickHouse server is running and actively rejecting bad logins.
  • Storage is mounted: ClickHouse can't check credentials if it can't read its config files from disk.

If we know the Clickhouse credentials, we can perform the health check:

% kubectl exec -it chi-clickhouse-ch-0-0-0 -n clickhouse -- clickhouse-client --user USER --password PASS --query "SELECT version(), uptime(), name FROM system.clusters"
Defaulted container "clickhouse" out of: clickhouse, clickhouse-backup
24.8.14.10544.altinitystable 501389 all-clusters
24.8.14.10544.altinitystable 501389 all-replicated
24.8.14.10544.altinitystable 501389 all-sharded
24.8.14.10544.altinitystable 501389 ch
24.8.14.10544.altinitystable 501389 default


The output above is exactly what we wanted to see. The database is responsive, healthy, and has an uptime of ~5.8 days (501,389 seconds). The version 24.8.14.10544.altinitystable indicates we are on a very recent, stable Altinity build.


2. Check Replication and Disk Health


Since you are using the Altinity Operator, ClickHouse is likely managing data across disks. You want to ensure the "System" tables report no errors. Run this to check if the disks are mounted and have space:

% kubectl exec -it chi-clickhouse-ch-0-0-0 \
-n clickhouse \
-- clickhouse-client --user USER --password PASS \
--query "SELECT name, path, formatReadableSize(free_space) AS free, formatReadableSize(total_space) AS total FROM system.disks"

Defaulted container "clickhouse" out of: clickhouse, clickhouse-backup
default /var/lib/clickhouse/ 89.60 GiB 95.80 GiB

If you have multiple replicas (e.g., a ch-0-1 pod), check for replication lag:

% kubectl exec -it chi-clickhouse-ch-0-0-0 \
-n clickhouse \
-- clickhouse-client --user USER --password PASS \
--query "SELECT type, last_exception, num_tries FROM system.replication_queue WHERE last_exception != ''"     

Defaulted container "clickhouse" out of: clickhouse, clickhouse-backup

Result: This should ideally be empty (or as above) . If you see exceptions here, your nodes aren't syncing correctly.


3. Verify the "Operator" View


The Altinity Operator provides a "Status" field in its Custom Resource that summarizes the health of the entire installation.

% kubectl get chi -n clickhouse

NAME         CLUSTERS   HOSTS   STATUS      HOSTS-COMPLETED   AGE
clickhouse   1          1       Completed                     123d

What to look for: Look for the STATUS column. It should say Completed. If it says InProgress or Error, the Operator is struggling to configure the cluster.

4. Check the Backup (Safety Net)

Since you saw clickhouse-backup pods earlier, verify that the last backup actually succeeded. This is your "point of no return" check before the upgrade.

kubectl logs -n clickhouse -l job-name=clickhouse-backup-cron-<TIMESTAMP> 

(Replace <TIMESTAMP> with one of the strings from your previous get all output, e.g., 29543400).

Look for: Done, Success, or Upload finished.

5. Check the Status of All Replicas 


To be absolutely sure the cluster is "Green" before you start the EKS upgrade, run this to check the status of all replicas in the cluster:

% kubectl exec -it chi-clickhouse-ch-0-0-0 -n clickhouse -- clickhouse-client --user USER --password PASS --query "SELECT replica_path, is_leader, is_readonly, future_parts FROM system.replicas"

is_readonly: Should be 0. If it's 1, the node can't write data (usually a Zookeeper issue).
is_leader: One of your replicas should be 1.


Summary Checklist


Test      Command Goal     Good Result
Ping      SELECT  1        1
Uptime    SELECT uptime()  >0
Storage   system.disks     Free space > 10%
Operator  kubectl get chi  Completed


Resources:



Introduction to ArgoCD


ArgoCD is a tool for deploying applications in Kubernetes cluster following the GitOps principles. 

Argo CD is a declarative, GitOps-based continuous delivery (CD) tool designed specifically for Kubernetes. It acts as a controller that monitors running applications, compares their live state to the desired state defined in a Git repository, and automatically syncs them to ensure consistency. 

Key Aspects of an Argo CD Application:
  • GitOps Source of Truth: Git repositories hold the desired state (manifests, Helm charts, Kustomize configs), which Argo CD pulls to apply to clusters.
  • Automated Synchronization: It automatically detects "OutOfSync" applications—where the live cluster state differs from Git—and can automatically or manually sync them to match.
  • Continuous Monitoring: It acts as a Kubernetes controller that continuously monitors applications.
  • Visualization & Management: It provides a web UI to visualize application structure, monitor status, and manage rollbacks.
  • Key Capabilities: Supports automated deployment, drift detection, and easy rollbacks. 

Argo CD is often used to ensure that the actual state of a Kubernetes cluster matches the configuration stored in a Git repository, making the deployment process more reliable and transparent.

ArgoCD GitOps flow:
  • Commit and push infra code changes (Terraform, Helm values etc) to the main branch of your GitHub repository.
  • ArgoCD Sync:
    • Manual: Log in to our ArgoCD Dashboard and click the "Sync" or "Refresh" button on the psmdb-default-sharded application.
    • Automatic: If "Self-Heal" or "Auto-Sync" is enabled, ArgoCD will detect the Git change within ~3 minutes and apply it to the cluster automatically.
  • Monitor the Operator: Once ArgoCD syncs, infra will get changed e.g. operators will see the updated custom resources and then trigger the further actions.

Installation


Installing ArgoCD in a Kubernetes cluster is primarily done using manifests or Helm charts. The most common and recommended approach for beginners is using the Official Argo CD Manifests. 

Prerequisites:
  • A running Kubernetes cluster (v1.22 or later).
  • kubectl command-line tool installed and configured to your cluster.
  • At least 2GB of available memory in your cluster.

Applications


ArgoCD defines custom Kubernetes objects like ApplicationAppProject, settings...which can be defined declaratively using Kubernetes manifests and deployed via kubectl apply to the ArgoCD namespace which is argocd by default.

To check ArgoCD applications:

% kubectl get applications -A        
NAMESPACE   NAME                    SYNC STATUS   HEALTH STATUS
argocd      my-app                  Synced        Progressing 



Dashboard


If you don't know the url of the ArgoCD dashboard, find the IP of the ArgoCD server and port-forward it:

% kubectl get svc argocd-server -n argocd
NAME          TYPE      CLUSTER-IP    EXTERNAL-IP PORT(S)        AGE
argocd-server ClusterIP 172.21.103.127 <none>     80/TCP,443/TCP 1d


By default, ArgoCD includes a built-in admin user with full super-user access. For better security practices, it is recommended that you use the admin account only for the initial configuration, then disable it once all required users have been added.

For this example, we can stick to this default admin user. Its password can be obtain via this command:

% kubectl get secret \
argocd-initial-admin-secret \
-n argocd \
-o jsonpath="{.data.password}" \
| base64 -d

Port forwarding:

% kubectl port-forward svc/argocd-server -n argocd 8080:443
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
Handling connection for 8080
...

We can now open the address http://localhost:8080/ in the browser to get the ArgoCD dashboard and log in with credentials above.

If there are no application registered with ArgoCD, we will see something like this:



---

Resources:


Thursday, 26 February 2026

Introduction to KubePug (Kubernetes tool)



What is KubePug?


KubePug (Kubernetes PreUpGrade Checker) is an open-source kubectl plugin and CLI tool designed to identify deprecated or deleted APIs in your Kubernetes cluster or manifest files before you perform an upgrade. 

Key Features


  • Deprecation Detection: Scans your live cluster or static YAML manifests to find resources using APIs that are slated for removal in future Kubernetes versions.
  • Replacement Guidance: Not only flags outdated APIs but also suggests the recommended replacement API and specifies the exact version where the deprecation or deletion occurs.
  • Version Targeting: Allows you to specify a target Kubernetes version (e.g., v1.31) to validate your current resources against that specific future release.
  • Flexible Data Source: It automatically downloads a frequently updated API definition file (every 30 minutes) to stay current with the latest Kubernetes releases. 

Why Use It?


As Kubernetes evolves, APIs are moved from alpha/beta to stable (GA), and older versions are eventually removed. If you upgrade your cluster without updating your manifests, those resources will fail to deploy or operate. KubePug provides a "pre-flight" check to prevent these breaking changes from reaching production.


Installation & Usage


You can install KubePug via Krew (where it is listed under the name deprecations) or as a standalone binary. 

Method                  Command
---------                    -------------
Install via Krew kubectl krew install deprecations
Scan Live Cluster kubectl deprecations --k8s-version=v1.30
Scan Manifest File kubepug --input-file=./my-manifest.yaml

Installation via Krew


% kubectl krew install deprecations
Updated the local copy of plugin index.
Installing plugin: deprecations
Installed plugin: deprecations
\
 | Use this plugin:
 | kubectl deprecations
 | Documentation:
 | https://github.com/rikatz/kubepug
 | Caveats:
 | \
 |  | * By default, deprecations finds deprecated object relative to the current kubernetes
 |  | master branch. To target a different kubernetes release, use the --k8s-version
 |  | argument.
 |  | 
 |  | * Deprecations needs permission to GET all objects in the Cluster
 | /
/
WARNING: You installed plugin "deprecations" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.


Execution



Once installed, the plugin is invoked using kubectl deprecations.

Scan Current Cluster


Check your live cluster for deprecated APIs against a specific target Kubernetes version:

kubectl deprecations --k8s-version=v1.33
                    
Error: failed to get apiservices: apiservices.apiregistration.k8s.io is forbidden: User "sso:user" cannot list resource "apiservices" in API group "apiregistration.k8s.io" at the cluster scope
time="2026-02-26T14:20:19Z" level=error msg="An error has occurred: failed to get apiservices: apiservices.apiregistration.k8s.io is forbidden: User \"sso:user\" cannot list resource \"apiservices\" in API group \"apiregistration.k8s.io\" at the cluster scope"


KubePug requires running user to have "list" permissions on resource "apiservices" in API group "apiregistration.k8s.io" at the cluster scope as otherwise the above error will appear.

If required permissions are in place:

% kubectl deprecations --k8s-version=v1.33

No deprecated or deleted APIs found

Kubepug validates the APIs using Kubernetes markers. To know what are the deprecated and deleted APIS it checks, please go to https://kubepug.xyz/status/


Scan Local Manifest Files


Validate static YAML files before applying them to a cluster:

kubectl deprecations --input-file=./my-manifests/


View Results in Different Formats


Output the findings in json or yaml for automated processing:

kubectl deprecations --format=json


Check for Help and Flags


See all available configuration options, such as using a custom database file or setting error codes:

kubectl deprecations --help


Key Parameters


--k8s-version: The Kubernetes release you intend to upgrade to (defaults to the latest stable).
--error-on-deprecated: Forces the command to exit with an error code if deprecated APIs are found, which is useful for CI/CD pipelines. 


---

Introduction to Krew (Kubernetes tool)




Krew is the official plugin manager for the kubectl command-line tool. Much like apt for Debian or Homebrew for macOS, it allows users to easily discover, install, and manage custom extensions that add new subcommands to Kubernetes. 

Core Functionality

  • Discovery: Users can search a community-curated index of over 200 plugins designed for tasks like security auditing, resource visualization, and cluster management.
  • Lifecycle Management: It automates the process of installing, updating, and removing plugins across different operating systems (Linux, macOS, and Windows).
  • Unified Interface: Once a plugin is installed via Krew, it is invoked directly through kubectl (e.g., kubectl <plugin-name>). 


Installation


MacOS example output:

% (
  set -x; cd "$(mktemp -d)" &&
  OS="$(uname | tr '[:upper:]' '[:lower:]')" &&
  ARCH="$(uname -m | sed -e 's/x86_64/amd64/' -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/')" &&
  KREW="krew-${OS}_${ARCH}" &&
  curl -fsSLO "https://github.com/kubernetes-sigs/krew/releases/latest/download/${KREW}.tar.gz" &&
  tar zxvf "${KREW}.tar.gz" &&
  ./"${KREW}" install krew
)
+-zsh:142> mktemp -d
+-zsh:142> cd /var/folders/8j/60m6_18j359_39ls0sr9ccvm0000gp/T/tmp.LKDSyQcO10
+-zsh:143> OS=+-zsh:143> uname
+-zsh:143> OS=+-zsh:143> tr '[:upper:]' '[:lower:]'
+-zsh:143> OS=darwin 
+-zsh:144> ARCH=+-zsh:144> uname -m
+-zsh:144> ARCH=+-zsh:144> sed -e s/x86_64/amd64/ -e 's/\(arm\)\(64\)\?.*/\1\2/' -e 's/aarch64$/arm64/'
+-zsh:144> ARCH=arm64 
+-zsh:145> KREW=krew-darwin_arm64 
+-zsh:146> curl -fsSLO https://github.com/kubernetes-sigs/krew/releases/latest/download/krew-darwin_arm64.tar.gz
+-zsh:147> tar zxvf krew-darwin_arm64.tar.gz
x ./LICENSE
x ./krew-darwin_arm64
+-zsh:148> ./krew-darwin_arm64 install krew
Adding "default" plugin index from https://github.com/kubernetes-sigs/krew-index.git.
Updated the local copy of plugin index.
Installing plugin: krew
Installed plugin: krew
\
 | Use this plugin:
 | kubectl krew
 | Documentation:
 | https://krew.sigs.k8s.io/
 | Caveats:
 | \
 |  | krew is now installed! To start using kubectl plugins, you need to add
 |  | krew's installation directory to your PATH:
 |  | 
 |  |   * macOS/Linux:
 |  |     - Add the following to your ~/.bashrc or ~/.zshrc:
 |  |         export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
 |  |     - Restart your shell.
 |  | 
 |  |   * Windows: Add %USERPROFILE%\.krew\bin to your PATH environment variable
 |  | 
 |  | To list krew commands and to get help, run:
 |  |   $ kubectl krew
 |  | For a full list of available plugins, run:
 |  |   $ kubectl krew search
 |  | 
 |  | You can find documentation at
 |  |   https://krew.sigs.k8s.io/docs/user-guide/quickstart/.
 | /
/


Add Krew path to PATHs:

% vi ~/.zshrc 

% cat ~/.zshrc   
...
export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH" <-- added manually


Reload .zshrc configuration file within the currently running Zsh terminal (or just restart the terminal):

% source ~/.zshrc

Installation verification:

% kubectl krew
krew is the kubectl plugin manager.
You can invoke krew through kubectl: "kubectl krew [command]..."

Usage:
  kubectl krew [command]

Available Commands:
  help        Help about any command
  index       Manage custom plugin indexes
  info        Show information about an available plugin
  install     Install kubectl plugins
  list        List installed kubectl plugins
  search      Discover kubectl plugins
  uninstall   Uninstall plugins
  update      Update the local copy of the plugin index
  upgrade     Upgrade installed plugins to newer versions
  version     Show krew version and diagnostics

Flags:
  -h, --help      help for krew
  -v, --v Level   number for the log level verbosity

Use "kubectl krew [command] --help" for more information about a command.

Common Commands


To use Krew, you must first install it as a kubectl plugin itself. Key commands include: 

kubectl krew update: Updates the local list of available plugins.
kubectl krew search: Finds plugins in the official Krew index.
kubectl krew install <plugin>: Installs a specific plugin.
kubectl krew list: Displays all plugins currently installed through Krew.
kubectl krew upgrade: Updates all installed plugins to their latest versions. 

Example:

% kubectl krew install deprecations
Updated the local copy of plugin index.
Installing plugin: deprecations
Installed plugin: deprecations
\
 | Use this plugin:
 | kubectl deprecations
 | Documentation:
 | https://github.com/rikatz/kubepug
 | Caveats:
 | \
 |  | * By default, deprecations finds deprecated object relative to the current kubernetes
 |  | master branch. To target a different kubernetes release, use the --k8s-version
 |  | argument.
 |  | 
 |  | * Deprecations needs permission to GET all objects in the Cluster
 | /
/
WARNING: You installed plugin "deprecations" from the krew-index plugin repository.
   These plugins are not audited for security by the Krew maintainers.
   Run them at your own risk.

Popular Plugins Managed by Krew 

  • ctx / ns: Rapidly switch between Kubernetes contexts and namespaces.
  • tree: Visualizes the hierarchy of Kubernetes resources in a tree view.
  • access-matrix: Displays an RBAC (Role-Based Access Control) matrix for server resources.
  • get-all: Lists all resources in a namespace, including those often missed by kubectl get all. 

Note on Security (!)


Plugins in the Krew index are community-contributed and are not audited for security by the Kubernetes maintainers; you should only install plugins from sources you trust. 


---

Introduction to Pluto (Kubernetes tool)

 

Pluto is:
  • CLI tool that helps users find deprecated Kubernetes API versions in your code repositories and Helm releases. 
  • It's especially useful when upgrading Kubernetes clusters, as it identifies resources that need updating before the upgrade.
  • It works against:
    • Live clusters
    • Helm charts
    • Raw YAML
  • Pluto will show which APIs are deprecated or removed, what version they were deprecated in, and what the replacement API should be.


Installation on Mac (https://pluto.docs.fairwinds.com/installation/#homebrew-tap):

% brew install FairwindsOps/tap/pluto

Let's see its CLI arguments:

% pluto                    
You must specify a sub-command.
A tool to detect Kubernetes apiVersions

Usage:
  pluto [flags]
  pluto [command]

Available Commands:
  completion            Generate the autocompletion script for the specified shell
  detect                Checks a single file or stdin for deprecated apiVersions.
  detect-all-in-cluster run all in-cluster detections
  detect-api-resources  detect-api-resources
  detect-files          detect-files
  detect-helm           detect-helm
  help                  Help about any command
  list-versions         Outputs a JSON object of the versions that Pluto knows about.
  version               Prints the current version of the tool.

Flags:
  -f, --additional-versions string        Additional deprecated versions file to add to the list. Cannot contain any existing versions
      --columns strings                   A list of columns to print. Mandatory when using --output custom, optional with --output markdown
      --components strings                A list of components to run checks for. If nil, will check for all found in versions.
  -h, --help                              help for pluto
      --ignore-deprecations               Ignore the default behavior to exit 2 if deprecated apiVersions are found. (Only show removed APIs, not just deprecated ones)
      --ignore-removals                   Ignore the default behavior to exit 3 if removed apiVersions are found. (Only show deprecated APIs, not removed ones)
      --ignore-unavailable-replacements   Ignore the default behavior to exit 4 if deprecated but unavailable apiVersions are found.
  -H, --no-headers                        When using the default or custom-column output format, don't print headers (default print headers).
  -r, --only-show-removed                 Only display the apiVersions that have been removed in the target version.
  -o, --output string                     The output format to use. (normal|wide|custom|json|yaml|markdown|csv) (default "normal")
  -t, --target-versions stringToString    A map of targetVersions to use. This flag supersedes all defaults in version files. (default [])
  -v, --v Level                           number for the log level verbosity

Use "pluto [command] --help" for more information about a command.


detect-files


To scan and detect deprecated APIs in manifest files in a directory:

% pluto detect-files -d /path/to/your/manifests

detect-files is for checking YAML files in our repositories/filesystem before deploying them - that's separate from detect-helm and detect-all-in-cluster cluster commands.

To target particular k8s version:

% pluto detect-files -d . --target-versions k8s=v1.33.0

If we use Terraform to deploy Helm charts, we might want to keep chart values in separate files (.yaml or .yaml.tpl) as otherwise we won't be able to use Pluto directly (we'd need to extract values into files first). For more details, see Where to keep Helm chart values in Terraform projects | My Public Notepad

detect-helm


To check Helm releases in the cluster (already deployed):

% pluto detect-helm -owide

To target particular k8s version:

% pluto detect-helm -owide --target-versions k8s=v1.33.0   
There were no resources found with known deprecated apiVersions.

detect-helm specifically checks Helm release metadata stored in our cluster (in secrets or configmaps) after Helm chart have been deployed. It looks at the manifests that Helm used to install releases, which might contain deprecated APIs even if they haven't been applied yet or are stored in Helm's history.


detect-all-in-cluster


To check all resources in the cluster:

% pluto detect-all-in-cluster -o wide
I0226 12:01:02.279788   47100 warnings.go:110] "Warning: v1 ComponentStatus is deprecated in v1.19+"
There were no resources found with known deprecated apiVersions.

detect-all-in-cluster scans all live resources currently running in our cluster by querying the Kubernetes API directly. It checks deployments, services, pods, etc. that are actively deployed.

detect-all-in-cluster does NOT include detect-helm or detect-files. Here's why they're separate:
  • detect-all-in-cluster sees the current state of resources
  • detect-helm sees Helm's stored templates and history, which may include:
    • Templated manifests that haven't been rendered yet
    • Old release revisions
    • Chart templates with deprecated APIs
  • Run both to get complete coverage!

Target a specific Kubernetes version:

% pluto detect-all-in-cluster --target-versions k8s=v1.33.0
I0226 12:02:26.551401   47113 warnings.go:110] "Warning: v1 ComponentStatus is deprecated in v1.19+"
There were no resources found with known deprecated apiVersions.

The warning message:

Warning: v1 ComponentStatus is deprecated in v1.19+

This is just Pluto itself triggering a Kubernetes API warning while scanning - it's not something wrong with our cluster resources.

The main result:

There were no resources found with known deprecated apiVersions.

This means all our cluster resources are using API versions that are still valid in Kubernetes v1.33.0 (our target version). This means:
  • Our cluster resources are already compatible with k8s v1.33.0
  • No manifests need updating before upgrading
  • No deprecated APIs that would be removed in v1.33.0

To be thorough before a k8s upgrade, we need to run all three commands:
  • detect-files
  • detect-helm
  • detect-all-in-cluster

---

Where to keep Helm chart values in Terraform projects


If we use Terraform to deploy Helm charts, we might be using one of these strategies to keep chart values:

  1. Values are in inline YAML string
  2. Values in separate .yaml file
  3. Values in separate YAML Template files (.yaml.tpl)
  4. Use Helm's set for Dynamic Values
  5. Multiple Values Files

(1) Values in inline YAML string


This is not ideal as problems with Inline YAML in Terraform include:
  • No syntax highlighting or validation - Easy to break YAML formatting
  • Hard to review in diffs - Changes are messy in PRs
  • Can't use standard tooling - No yamllint, Pluto, or other YAML tools
  • Mixing concerns - Infrastructure code mixed with application config
  • Escaping nightmares - Terraform string interpolation conflicts with Helm templating

Example:

resource "helm_release" "app" {
  values = [<<-EOT
    replicaCount: ${var.replicas}
    image:
      repository: myapp
      tag: ${var.tag}
    service:
      type: LoadBalancer
  EOT
  ]
}


(2) Separate Values Files 


Keep values in YAML files, reference them in Terraform.
This is a better approach because:
  • Clean separation
  • Easy to validate with standard tools
  • Better diffs
  • Can use Pluto directly: pluto detect-files -d .

Example:

main.tf:

resource "helm_release" "my_app" {
  name       = "my-app"
  chart      = "my-chart"
  repository = "https://charts.example.com"
  
  values = [
    file("${path.module}/helm-values.yaml")
  ]
}


(3) Templated Values Files


Use Terraform's templatefile() to inject dynamic values:


helm-values.yaml.tpl:

replicaCount: ${replica_count}
image:
  repository: ${image_repo}
  tag: ${image_tag}
ingress:
  enabled: ${enable_ingress}
  host: ${hostname}

main.tf:

resource "helm_release" "my_app" {
  name  = "my-app"
  chart = "my-chart"
  
  values = [
    templatefile("${path.module}/helm-values.yaml.tpl", {
      replica_count  = var.replica_count
      image_repo     = var.image_repository
      image_tag      = var.image_tag
      enable_ingress = var.enable_ingress
      hostname       = var.hostname
    })
  ]
}

Pros:

  • Still gets variable injection
  • Can be validated as YAML (with placeholders)
  • Clean and readable


(4) Use Helm's set for Dynamic Values


Keep static config in files, override specific values:


resource "helm_release" "my_app" {
  name       = "my-app"
  chart      = "my-chart"
  
  # Base values from file
  values = [
    file("${path.module}/helm-values.yaml")
  ]
  
  # Override specific values dynamically
  set {
    name  = "image.tag"
    value = var.image_tag
  }
  
  set {
    name  = "replicaCount"
    value = var.replica_count
  }
  
  set_sensitive {
    name  = "secret.password"
    value = var.db_password
  }
}

Pros:
  • Clear what's dynamic vs static
  • Base values file can be validated
  • Sensitive values handled properly

Here is the example how we can migrate inline YAML from the above to templated file:

helm-values.yaml:

image:
  repository: myapp
service:
  type: LoadBalancer


main.tf:

resource "helm_release" "app" {
  values = [
    file("${path.module}/helm-values.yaml")
  ]
  
  set {
    name  = "replicaCount"
    value = var.replicas
  }
  
  set {
    name  = "image.tag"
    value = var.tag
  }
}

Now we can run: 

% pluto detect-files -f helm-values.yaml



(5) Multiple Values Files


We can layer our configuration:

resource "helm_release" "my_app" {
  name  = "my-app"
  chart = "my-chart"
  
  values = [
    file("${path.module}/helm-values-base.yaml"),
    file("${path.module}/helm-values-${var.environment}.yaml")
  ]
}

---