Skip to content

Extending kubectl

Couple years ago K8s community introduced an easy way to extend kubectl via plugins.

Plugins are in fact "applications" (executable files) named kubectl-<plugin_name>, that are executed when you call kubectl plugin_name.

Krew is the plugin manager for kubectl command-line tool and it's maintained by the Kubernetes SIG CLI community.

Krew helps you:

  • discover kubectl plugins,
  • install them on your machine,
  • and keep the installed plugins up-to-date. There are 207 kubectl plugins currently distributed on Krew.

Krew

Krew is an example of such a kubectl plugin that act as a plugin manager for kubectl. It pre-dates the plugin addition in kubectl and may seem useless now, but it still has it's role to play.

Install

Please refer to the official install doc.

krew install
brew install krew

This one is using krew itself to install krew.

Note

krew installs plugins in $KREW_ROOT if you set it or $HOME/.krew. You have to then ensure $HOME/.krew is in your path.

Add this to your .zshrc or .bashrc:

export PATH="${KREW_ROOT:-$HOME/.krew}/bin:$PATH"
(
  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
)
  • Make sure git is installed.
  • Download krew.exe from the Releases page to a directory.
  • Launch a command prompt (cmd.exe) with administrator privileges (since the installation requires use of symbolic links) and navigate to that directory.
  • Run the following command to install krew:
    .\krew install krew
    
  • Add the %USERPROFILE%\.krew\bin directory to your PATH environment variable (how?)
  • Launch a new command-line window.
  • Run kubectl krew to check the installation.

Usage

kubectl krew update

kubectl krew list
output
PLUGIN  VERSION
ctx     v0.9.4
krew    v0.4.1
ns      v0.9.4
whoami  v0.0.36
kubectl krew search
output
NAME                            DESCRIPTION                                         INSTALLED
access-matrix                   Show an RBAC access matrix for server resources     no
blame                           Show who edited resource fields.                    no
cert-manager                    Manage cert-manager resources inside your cluster   no
ctx                             Switch between contexts in your kubeconfig          yes
...

Krew Plugins

Install plugins that will be used in the tutorial:

  • ctx: current cluster Context and quick context changes
  • ns: current Namespace and quick namespace changes
  • whoami: who the cluster thinks you are from your authentication
  • who-can: RBAC rules introspection
  • view-secret: directly view secret content without having to decode

Install them with this command:

kubectl krew install neat ctx ns whoami who-can view-secret

Generating the application manifest

The GoWebApp application was previously deployed with a hardcoded password for Mysql. This is not optimal and not recommended. It was done like this because it's a DEMO, and not real production cluster.

Note

Usually all application's deployment files (the YAML) should be managed in a versioned repository and should never be modified directly on the cluster.

For this DEMO, still, we are going to use the currently deployed application and modify it.

Now that the application is running and everything is fine, it is a good idea to store the resulting yaml in our own repo.

First dump the gowebapp deployment into a file. By using the --output yaml (-o yaml) option, kubectl will dump the full file, including some fields internal to the current deployment that are not needed:

k get deploy gowebapp --output yaml
output
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"run":"gowebapp"},"name":"gowebapp","namespace":"default"},"spec":{"replicas":1,"selector":{"matchLabels":{"run":"gowebapp"}},"template":{"metadata":{"labels":{"run":"gowebapp"}},"spec":{"containers":[{"env":[{"name":"DB_PASSWORD","value":"rootpasswd"}],"image":"ghcr.io/cloud-native-canada/k8s_setup_tools:app","livenessProbe":{"httpGet":{"path":"/register","port":9000},"initialDelaySeconds":15,"timeoutSeconds":5},"name":"gowebapp","ports":[{"containerPort":9000}],"readinessProbe":{"httpGet":{"path":"/register","port":9000},"initialDelaySeconds":25,"timeoutSeconds":5},"resources":{"limits":{"cpu":"50m","memory":"100Mi"},"requests":{"cpu":"20m","memory":"10Mi"}}}]}}}}
  creationTimestamp: "2022-10-25T16:32:28Z"
  generation: 1
  labels:
    run: gowebapp
  name: gowebapp
  namespace: default
  resourceVersion: "2662"
  uid: c0085020-845c-468e-a95e-9bb2e908dc2b
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      run: gowebapp
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: gowebapp
    spec:
      containers:
      - env:
        - name: DB_PASSWORD
          value: rootpasswd
        image: ghcr.io/cloud-native-canada/k8s_setup_tools:app
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /register
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 15
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        name: gowebapp
        ports:
        - containerPort: 9000
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /register
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 25
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        resources:
          limits:
            cpu: 50m
            memory: 100Mi
          requests:
            cpu: 20m
            memory: 10Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2022-10-25T16:33:09Z"
    lastUpdateTime: "2022-10-25T16:33:09Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2022-10-25T16:32:28Z"
    lastUpdateTime: "2022-10-25T16:33:09Z"
    message: ReplicaSet "gowebapp-5994456fcb" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1

Here, the status section is useless, as it relates to the current deployment. So are the UID or creationTimestamp fields. It is not recommended to store these values in your GitOps repo.

Krew has a little plugin to trim off the un-needed parts of exported resources: Neat.

To use Neat, just add the neat keyword between kubectl and the get command:

k neat get deploy gowebapp --output yaml
output
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  labels:
    run: gowebapp
  name: gowebapp
  namespace: default
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      run: gowebapp
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: gowebapp
    spec:
      containers:
      - env:
        - name: DB_PASSWORD
          value: rootpasswd
        image: ghcr.io/cloud-native-canada/k8s_setup_tools:app
        imagePullPolicy: IfNotPresent
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /register
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 15
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        name: gowebapp
        ports:
        - containerPort: 9000
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /register
            port: 9000
            scheme: HTTP
          initialDelaySeconds: 25
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 5
        resources:
          limits:
            cpu: 50m
            memory: 100Mi
          requests:
            cpu: 20m
            memory: 10Mi
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      terminationGracePeriodSeconds: 30

When needed, you can then save the file for backup or later use:

# k neat get deploy gowebapp --output yaml > neat-app-deployment.yaml

manage Kubernetes namespaces

Before

kubectl get pods -n kube-system
kubectl get pods -n kubecon

After

The Krew ns plugin is used to switch context and set it as the new default. This change is actually persisted in the ~/.kube/config file.

As kubectl get namespaces, the k ns command will dump the existing namespaces from the cluster.

Switch to kube-system namespace:

k ns kube-system
output
Context "kind-dev" modified.
Active namespace is "kube-system".

krew ns

Let's Deploy aplication in a new namespace

  • k8s context

kubectl config current-context
kind-dev

  • All contexts

We can list all Kubernetes context using kubectl:

kubectl config  get-contexts
CURRENT   NAME        CLUSTER     AUTHINFO    NAMESPACE
*         kind-dev    kind-dev    kind-dev

Before we deploy a new application, remove the application from the default namespace:

k delete -f ~/demo/base

Now create a new namespace:

k create namespace gowebapp

Then switch to this new namespace:

k ns gowebapp

Now install the GoWebApp application into this new namespace:

k apply -f ~/demo/base

manage Kubernetes context

Before

  • k8s context

kubectl config current-context
kind-dev

  • All contexts

We can list all Kubernetes context using kubectl:

kubectl config  get-contexts
CURRENT   NAME        CLUSTER     AUTHINFO    NAMESPACE
*         kind-dev    kind-dev    kind-dev

After

kubectl is using the notion of contexts to define which cluster you know and which one is actuvelly being used.

All this is defined in the $HOME/.kube/config file. This file can get quite large and difficult to work with.

kubectl has a default command to change context:

kubectl config get-contexts
output
CURRENT   NAME        CLUSTER     AUTHINFO    NAMESPACE
*         kind-dev    kind-dev    kind-dev    default
          kind-stg    kind-stg    kind-stg

Switch context to the stg cluster:

kubectl config use-context kind-stg

While this is not that long to type, we can do better with ctx plugin. Also, when using kubecolor, the current context will be highlighted:

k ctx
output
kind-dev
kind-stg

You can also change context quickly by just appending the name of the target context to the same command:

k ctx kind-stg
output
kind-dev
kind-stg

Finaly you can delete a context (but don't do it right now):

k ctx -d kind-stg

krew ctx

View Secrets

Secrets in Kubernetes are an enhenced version of ConfigMaps: they are base64 encoded !

First, create a secret:

k create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret -o yaml --dry-run=true
output
apiVersion: v1
data:
  key1: c3VwZXJzZWNyZXQ=
  key2: dG9wc2VjcmV0
kind: Secret
metadata:
  creationTimestamp: null
  name: my-secret

the values are un readable because they are base64 encoded.

Apply that to the cluster:

k create secret generic my-secret \
  --from-literal=key1=supersecret \
  --from-literal=key2=topsecret \
  -o yaml --dry-run=true | k apply -n default -f -

It is possible to read a secret value by using a templated output option:

kubectl get secret -n default my-secret --output=go-template={{.data.key1}} | base64 --decode
output
supersecret

The Krew plugin view-secret does all that in one simple call:

k view-secret my-secret key1
output
supersecret

Next

Continue to Multi Context