How to Create Service Account in Kubernetes and VMware TKGI (With Token and Kubeconfig)

Last updated: August 2026

In this lesson, you will learn how to create a service account in Kubernetes and VMware TKGI (Same Steps is also used for (Tanzu TKG/VKS, TKGI, AKS, EKS, GKS, OpenShift)

Understanding The Subject Matter

Managing secure access to Kubernetes clusters is critical, especially when giving third-party vendors read-only access. In this guide, I will show you step-by-step, how to create a service account (SA) with read-only permissions, generate a bearer token (Access token), and create a working kubeconfig for both vanilla Kubernetes and other Kubernetes distributions (VMware TKGI clusters, TKG VKS, AKS, EKS, GKS, OpenShift, Rancher)

We will also cover common pitfalls like RBAC restrictions and PEM parsing issues with CA certificates.

Background

  • Kubernetes service accounts allow automated processes or external tools to authenticate and interact with the cluster.
  • Modern Kubernetes versions (v1.24+) no longer automatically store service account secrets as accessible Secret objects. Instead, kubectl create token <sa> is the recommended method for generating tokens.
  • By default, the built-in view role allows namespaced read-only access (pods, deployments, services, etc.) but does not include cluster-level resources like nodes.

ACTION TIME

Part 1 (Using Imperative Approach)

Step 1 – Connect to Your Cluster

Make sure you are logged in as a cluster admin:

ansu@mastern-01:~$ kubectl config current-context

kubernetes-admin@kubernetes

Step 2 – Create the Service Account

ansu@mastern-01:~$ kubectl create serviceaccount cimtrack-sa -n default

serviceaccount/cimtrack-sa created
ansu@mastern-01:~$ kubectl get sa cimtrack-sa -n default

NAME          SECRETS   AGE
cimtrack-sa   0         3m25s

Step 3 – Bind Read-Only Role

Bind the service account to the built-in view cluster role:

ansu@mastern-01:~$ kubectl create clusterrolebinding cimtrack-sa-readonly \
--clusterrole=view \
--serviceaccount=default:cimtrack-sa

clusterrolebinding.rbac.authorization.k8s.io/cimtrack-sa-readonly created

where,

cimtrack-sa-readonly = clusterolebinding name

cimtrack-sa = service account name

default = namespace where the service account name will be created.

ansu@mastern-01:~$ kubectl get clusterrolebinding cimtrack-sa-readonly

NAME                   ROLE               AGE
cimtrack-sa-readonly   ClusterRole/view   31s

For some service accounts, you may want to use/bind the admin role to them.

To bind the admin role to the service account, change the cluster-role above to admin

ansu@mastern-01:~$ kubectl create clusterrolebinding cimtrack-sa-admin \
--clusterrole=admin \
--serviceaccount=default:cimtrack-sa

clusterrolebinding.rbac.authorization.k8s.io/cimtrack-sa-admin created

Step 4 – Generate the bearer/access Token

for the – -duration flag, it means I want the token to be valid for 8760 hours which is one year. You can use any duration you want to

ansu@mastern-01:~$ kubectl create token cimtrack-sa -n default --duration=8760h

eyJhbGciOiJSUzI1NiIsImtpZCI6IkhSOE16cElXQVlVZGVvSi1xY2NKN21hZnBpalpNanBmMF9JSDJhVFo3dWsifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxODA0Njg1NDU0LCJpYXQiOjE3NzMxNDk0NTQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZW

Copy the token — this is your bearer/access token.

Note: Kubernetes no longer stores service account secrets as Secret objects for tokens by default. You must use kubectl create token to generate them, copy, and store it somewhere safe. If you don’t store it, you would have to regenerate a new access token.

We can optionally test if the token is working by creating a kubeconfig file

Step 5 – Create a Kubeconfig for Testing

Declare your variables

CLUSTER_NAME=$(kubectl config current-context)
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
TOKEN=<paste-your-token-here>

The command will look like this.

ansu@mastern-01:~$ CLUSTER_NAME=$(kubectl config current-context)
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
CA_CERT=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IkhSOE16cElXQVlVZGVvSi1xY2NKN21hZnBpalpNanBmMF9JSDJhVFo3dWsifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxODA0Njg1NDU0LCJpYXQiOjE3NzMxNDk0NTQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZW

Create the kubeconfig file

ansu@mastern-01:~$ cat <<EOF > cimtrack-sa.kubeconfig
apiVersion: v1
kind: Config
clusters:
- cluster:
    server: ${APISERVER}
    insecure-skip-tls-verify: true
  name: ${CLUSTER_NAME}
contexts:
- context:
    cluster: ${CLUSTER_NAME}
    user: cimtrack-sa
  name: cimtrack-sa-context
current-context: cimtrack-sa-context
users:
- name: cimtrack-sa
  user:
    token: ${TOKEN}
EOF

Step 6 – Test the Service Account

ansu@mastern-01:~$ kubectl --kubeconfig=cimtrack-sa.kubeconfig get pods -A

NAMESPACE          NAME                                               READY   STATUS      RESTARTS           AGE
argocd             argocd-application-controller-0                    0/1     Running     1 (42d ago)        256d
argocd             argocd-applicationset-controller-bdd44f68b-cl7hp   1/1     Running     1 (42d ago)        256d
argocd             argocd-dex-server-5d77b5674c-nlxz2                 1/1     Running     0                  38d
argocd             argocd-notifications-controller-644d599cff-6mct4   1/1     Running     0                  38d
argocd             argocd-redis-58c46c8f79-cv8h6                      1/1     Running     0                  38d
argocd             argocd-repo-server-5bf7c558f7-kxb9l                1/1     Running     0                  38d

Part 2 (Using Declarative Approach)

I will be creating all the objects/resources in the default namespace because I want the cimtrack tool to be able to access all the folders/objects in every namespaces in the cluster.

1. create a service account

ansu@mastern-01:~$ vi sa.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: testingteam
  namespace: default


ansu@mastern-01:~$ k apply -f sa.yaml

serviceaccount/testingteam created

2. Create a clusterrolebinding

ansu@mastern-01:~$ vi crb.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: testingteam-default-binding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: testingteam
  namespace: default
ansu@mastern-01:~$ k apply -f crb.yaml

clusterrolebinding.rbac.authorization.k8s.io/testingteam-default-binding created

What this does

This binds the testingteam ServiceAccount to the cluster-admin ClusterRole, which means:

  • Full cluster access
  • Can manage nodes, pods, deployments, RBAC, etc.
  • Equivalent to cluster administrator

Important (especially for production):
Giving cluster-admin is very powerful and usually avoided unless absolutely necessary.

3. Generate the bearer/access Token

for the – -duration flag, it means I want the token to be valid for 8760 hours which is one year. You can use any duration you want to

ansu@mastern-01:~$ kubectl create token cimtrack-sa -n default --duration=8760h

U0LCJpYXQiOjE3NzMxNDk0NTQsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZW
...............

We can optionally test if the token is working by creating a kubeconfig file as done when we used the imperative approach (Part 1)

Sometimes, developers or monitoring team requires one creates a permanent kubeconfig file for the service accounts so that they are able to use it. You can click this link to do that.

Important note (modern Kubernetes)

In Kubernetes v1.24+, service accounts do NOT automatically create secrets anymore. The recommended method is:

kubectl create token testingteam -n default

The token will only last for 1 hour with the above command, you can specify the duration you want the token to last just as we did in part1A

Your static secret method still works, but Kubernetes now prefers short-lived tokens generated on demand for better security.

You will notice that in part 1 and Part 2, we didn’t create a secret because we don’t want the token to last forever.

Just in case you want the token to last forever, which is not recommended for a service account, except when necessary, you can follow the steps below.

1. Create a secret

ansu@mastern-01:~$ vi secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: testingteam-token
  namespace: default
  annotations:
    kubernetes.io/service-account.name: testingteam
type: kubernetes.io/service-account-token
ansu@mastern-01:~$ k apply -f secret.yaml

secret/testingteam-token created

2. Generate, extract and print the token

ansu@mastern-01:~$ TOKEN=$(kubectl -n default get secret testingteam-token -o jsonpath='{.data.token}' | base64 -d)
ansu@mastern-01:~$ echo $TOKEN

eyJhbGciOiJSUzI1NiIsImtpZCI6IkhSOE16cElXQVlVZGVvSi1xY2NKN21hZnBpalpNanBmMF9JSDJhVFo3dWsifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlY

Monitoring Team May ask for the cluster endpoint

When creating a service account, especially for a third application outside of the cluster to integrate with, its a good idea to always confirm your cluster API endpoint/server which can be given to the monitoring team or any team that requires it.

ansu@mastern-01:~$ kubectl cluster-info

Kubernetes control plane is running at https://10.10.10.145:6443
CoreDNS is running at https://10.10.10.145:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

OR

ansu@mastern-01:~$  kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'

https://10.10.10.145:6443ansu@mastern-01:~$

This is the IP of my LB (layer 4) – The one that load-balances the cluster control plane. if you created a DNS record for it, and its resolvable in your environment, that can also be used.

what you are trying to do is to extract the Kubernetes API server URL from your kubeconfig.

What this command is doing

You are trying to extract the Kubernetes API server URL from your kubeconfig.

Breakdown:

  • kubectl config view → shows your kubeconfig
  • --minify → shows only the current context
  • -o jsonpath=... → extracts a specific field
  • .clusters[0].cluster.server → the API server endpoint

Friendly commands

ansu@mastern-01:~$ kubectl  get sa

NAME          SECRETS   AGE
cimtrack-sa   0         2d1h
default       0         264d
testingteam   0         110m
ansu@mastern-01:~$ kubectl  get sa cimtrack-sa -o yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2026-03-10T13:24:49Z"
  name: cimtrack-sa
  namespace: default
  resourceVersion: "65636552"
  uid: d0679e19-3255-4f77-ab95-cd448544432a1
ansu@mastern-01:~$ kubectl  get sa testingteam -o yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"name":"testingteam","namespace":"default"}}
  creationTimestamp: "2026-03-12T13:12:01Z"
  name: testingteam
  namespace: default
  resourceVersion: "66101435"
  uid: 6a9f7e16-2e81-4f06-9344-1453e44a4816
ansu@mastern-01:~$ k get clusterrolebinding |grep testing

testingteam-default-binding                              ClusterRole/cluster-admin  
ansu@mastern-01:~$ k get clusterrolebinding |grep cimtrack

cimtrack-sa-readonly                                     ClusterRole/view                                                                   2d1h

ansu@mastern-01:~$ k get clusterrolebinding testingteam-default-binding -o yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"rbac.authorization.k8s.io/v1","kind":"ClusterRoleBinding","metadata":{"annotations":{},"name":"testingteam-default-binding"},"roleRef":{"apiGroup":"rbac.authorization.k8s.io","kind":"ClusterRole","name":"cluster-admin"},"subjects":[{"kind":"ServiceAccount","name":"testingteam","namespace":"default"}]}
  creationTimestamp: "2026-03-12T13:17:27Z"
  name: testingteam-default-binding
  resourceVersion: "66102307"
  uid: 0f3d6b88-2fea-44b3-a7cd-2209dd0c90ac
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: testingteam
  namespace: default
service account in kubernetes and VMware TKGI

ansu@mastern-01:~$ k get clusterrolebinding cimtrack-sa-readonly -o yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: "2026-03-10T13:28:58Z"
  name: cimtrack-sa-readonly
  resourceVersion: "65637225"
  uid: a4c3a5a0-db79-45e0-8516-a0f5a7947125
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: view
subjects:
- kind: ServiceAccount
  name: cimtrack-sa
  namespace: default

Other Helpful Kubernetes Commands

1. How To Check Your Current Context

# kubectl config current-context

2. How To List all Available Contexts

# kubectl config get-contexts

You will see something like

CURRENT   NAME             CLUSTER          AUTHINFO
*         dev-cluster      dev              dev-user
          prod-cluster     prod             prod-user

3. How To Switch to Another Context (switching to ‘prod-cluster’ context)

kubectl config use-context prod-cluster

Click to Watch Video on How to Create a service account in kubernetes and VMware TKGI

1 Comment

  1. Hi Tekneed,

    I hope you are doing well.

    My name is Afoke, and I have been following your YouTube content, which I have found very insightful and helpful.

    I am currently preparing for the EX374 (Ansible Automation Platform) exam and came across your recommendation to reach out for practice questions, study materials, and guidance. I would really appreciate any resources or direction you can share to support my preparation.

    Thank you for the value you provide to the tech community.

    I look forward to hearing from you.

    Kind regards,
    Afoke

Leave a Reply

Your email address will not be published.


*