Create and Manage Kubernetes Deployments and Stateful Sets in Go with client-go API
Introduction
The Kubernetes API Server provides a REST API interface for clients to interact with it and execute operations such as creating Deployment
s etc. But, a client application does not have to understand and use the lower-level HTTP
operations (such as GET
, PUT
, POST
, PATCH
etc.) since there are multiple client libraries for different languages.
This guide covers how to manage a Deployment
and StatefulSet
using the Kubernetes Go client. Before we get into the details, let's briefly understand some of the concepts.
Deployment
resource
A Pod
is the basic foundation of running an application on Kubernetes. However, simply deploying a single instance of a Pod
will not suffice. A Pod
is not rescheduled in the case of a Kubernetes node failure and therefore can lead to an outage. A ReplicaSet
is a Kubernetes resource that controls multiple, identical instances of a Pod
. It can scale the number of Pod
s (replicas) up or down on-demand. It can also roll out a new version of the application across all replicas.
A Deployment
abstracts the functionality of ReplicaSet
and manages it. You do not have to create, modify, or delete ReplicaSet
s directly. The Deployment
keeps a history of application versions and can roll back to an older version if required. Just like a ReplicaSet
, a Deployment
can also scale the number of replicas.
While updating a Deployment
, it is possible to pause rollouts for that Deployment
before triggering one or more updates. Once you're ready to apply the changes, simply resume rollouts for the Deployment
. This approach allows you to apply multiple fixes in between pausing and resuming without triggering unnecessary rollouts. Kubernetes also supports Canary Deployments. If you need to roll out releases to a specific set of users, you can create multiple Deployment
s (one for each release).
StatefulSet
resource
Just like a Deployment
, a StatefulSet
also manages Pod
s. However, unlike a Deployment
, a StatefulSet
maintains a sticky identity for each of their Pod
s. Although Pods
in a StatefulSet
are created from the same spec, they are not interchangeable. Each Pod
has a persistent identifier that it maintains across any rescheduling. Although individual Pod
s in a StatefulSet
can fail, the persistent Pod
identifiers make possible to match existing volumes to the new Pods
that replace any that have failed. That's why you can use StatefulSet
s are useful for applications that require one or more of the following:
- Persistent state store.
- Unique identifiers for each application instance.
- Ordered, graceful deployment and scaling.
- Ordered, automated rolling updates.
StatefulSet
Pods
have a unique identity that consists of an ordinal, a stable network identity, and stable storage. The identity sticks to the Pod
, regardless of which node it's (re)scheduled on. For a StatefulSet
with N
replicas, each Pod
in the StatefulSet
will be assigned an integer ordinal, that is unique over the Set. By default, pods will be assigned ordinals from 0
up through N-1
. Scaling a StatefulSet
creates a new Pod
instance with the next unused ordinal index. For example, if you scale up from two to three instances, the new instance will get index 2
. StatefulSet
s scale down only one Pod
instance at a time.
For persistence, a StatefulSet
has to create PersistentVolumeClaim
s. The PersistentVolume
s for the claims can either be provisioned up-front or just in time through dynamic provisioning of PersistentVolume
s. After a scale down operation, only the Pod
s are deleted, not the PersistentVolumeClaim
s. You’re need to delete PersistentVolumeClaims
manually to release the underlying PersistentVolume
.
StatefulSet
provides two policies for Pod
management:
OrderedReady
- This is the default policy.Parallel
- When this policy is in effect, allPod
s are created and deleted in parallel. This policy only applies to scaling operations, not updates.
Kubernetes Go APIs
Kubernetes Go client library is a high-level library that can be used by developers to interact with the Kubernetes API using the Go language. This library brings together the Kubernetes API and along with other libraries, providing a Scheme that is pre-configured with Kubernetes API’s objects and a RESTMapper
implementation for the Kubernetes API. It also provides a set of clients to use to execute operations on the resources of the Kubernetes API in a simple way.
Since Kubernetes is backward-compatible it is possible to use older versions of client-go
with newer versions of clusters. But bear in mind that bug fixes are back-ported to previous client-go
releases, not new features.
In addition to the the Go client library, the other two important Go libraries needed to work with the Kubernetes API are the apimachinery and the api.
- The API Machinery - It is a generic library that takes care of serializing data between Go structures and objects written in the
JSON
format. This makes it possible for developers of clients, but also API Servers, to write data using Go structures and transparently use these resources inJSON
during theHTTP
exchanges. - The API Library - It is a collection of Go structures that are needed to work in Go with the resources defined by the Kubernetes API.
Prerequisites
Before following the steps in this guide, you need to:
Install kubectl on your local workstation. It is a Kubernetes command-line tool that allows us to run commands against Kubernetes clusters.
Deploy a Vultr Kubernetes Engine (VKE) cluster using the Reference Guide. Once it's deployed, from the Overview tab, click the Download Configuration button in the upper-right corner to download your
kubeconfig
file and save it to a local directory.Point
kubectl
to Vultr Kubernetes Engine cluster by setting theKUBECONFIG
environment variable to the path where you downloaded the clusterkubeconfig
file in the previous step.export KUBECONFIG=<path to VKE kubeconfig file>
Verify the same using the following command:
kubectl config current-context
Install Go programming language (version 1.18 or higher) on your local workstation.
Install a recent version of kubectl on your local workstation.
Initialize the project
Create a directory and switch to it:
mkdir k8s-go-client-example cd k8s-go-client-example
Create a new Go module:
go mod init k8s-go-client-example
This will create a new
go.mod
fileCreate a new file
main.go
:touch main.go
Import libraries
To import required Go modules, add the following to main.go
file:
package main
import (
"context"
"flag"
"fmt"
"log"
"path/filepath"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
Add the init
function
Add the code below to main.go
:
var clientset *kubernetes.Clientset
const deploymentName = "test-deployment"
const statefulSetName = "test-statefulset"
const serviceName = "nginx-service"
func init() {
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
log.Fatal(err)
}
clientset, err = kubernetes.NewForConfig(config)
if err != nil {
log.Fatal(err)
}
}
The init
function takes care of the following:
- Access the
config
file in the.kube
folder under the user's home directory. - Uses the path to kube config file to create a
*rest.Config
by invokinclientcmd.BuildConfigFromFlags
. - Creates a
*kubernetes.Clientset
by invoking thekubernetes.NewForConfig
passing in the*rest.Config
Add the function to create a Deployment
Add the createDeployment
function to main.go
file:
func createDeployment() {
client := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
numReplicas := int32(2)
deployment := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: deploymentName,
},
Spec: appsv1.DeploymentSpec{
Replicas: &numReplicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "demo-app",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "demo-app",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "web-app",
Image: "nginx",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
},
},
},
},
},
}
fmt.Println("creating deployment", deploymentName)
_, err := client.Create(context.Background(), deployment, metav1.CreateOptions{})
if err != nil {
log.Fatal("failed to create deployment", err)
}
fmt.Println("successfully created deployment", deploymentName)
}
If successful, this function creates a Deployment
named test-deployment
.
- First, it gets a
v1.DeploymentInterface
instance by callingclientset.AppsV1().Deployments()
with the default namespace. - Prepares a new
Deployment
definition. - Invokes
DeploymentInterface.Create
to initiateDeployment
creation.
Add the main
function
Finally, add the main function
which will invoke the createDeployment
function.
func main() {
createDeployment()
}
Run the program to create a new Deployment
Fetch the dependencies:
go mod tidy
Run the program:
go run main.go
You should see the following output:
creating deployment test-deployment successfully created deployment test-deployment
Confirm that the
Deployment
was created:kubectl get deployment/test-deployment
You should see the following output:
NAME READY UP-TO-DATE AVAILABLE AGE test-deployment 2/2 2 2 31s
Verify the
Pod
s created by the Deployment:kubectl get pods -l=app=demo-app
You should see the following output (the
Pod
names might differ in your case):NAME READY STATUS RESTARTS AGE test-deployment-6d67cdd94-q5wgv 1/1 Running 0 3m5s test-deployment-6d67cdd94-srz7m 1/1 Running 0 3m5s
Add the function to delete a Deployment
Add the deleteDeployment
function to main.go
:
func deleteDeployment() {
client := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
fmt.Println("deleting deployment", deploymentName)
err := client.Delete(context.Background(), deploymentName, metav1.DeleteOptions{})
if err != nil {
log.Fatal("failed to delete deployment", err)
}
fmt.Println("successfully deleted deployment", deploymentName)
}
Update the main
function - comment out the createDeployment
function and add the deleteDeployment
function
func main() {
//createDeployment()
deleteDeployment()
}
Run the program to delete an existing Deployment
To run the program:
go run main.go
You should see the following output:
deleting deployment test-deployment successfully deleted deployment test-deployment
Confirm that the
Deployment
was deleted:kubectl get deployment/test-deployment
You should see the following output:
Error from server (NotFound): deployments.apps "test-deployment" not found
Confirm that
Pod
s created by theDeployment
were also deleted:kubectl get pods -l=app=demo-app
You should see the following output:
No resources found in default namespace.
Add the function to create a StatefulSet
Add the createStatefulSet
function to main.go
:
func createStatefulSet() {
client := clientset.AppsV1().StatefulSets(apiv1.NamespaceDefault)
numReplicas := int32(2)
statefulset := &appsv1.StatefulSet{
ObjectMeta: metav1.ObjectMeta{
Name: statefulSetName,
},
Spec: appsv1.StatefulSetSpec{
ServiceName: serviceName,
Replicas: &numReplicas,
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "nginx-app",
},
},
Template: apiv1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "nginx-app",
},
},
Spec: apiv1.PodSpec{
Containers: []apiv1.Container{
{
Name: "web-app",
Image: "nginx",
Ports: []apiv1.ContainerPort{
{
Name: "http",
Protocol: apiv1.ProtocolTCP,
ContainerPort: 80,
},
},
VolumeMounts: []apiv1.VolumeMount{
{Name: "www", MountPath: "/usr/share/nginx/html"},
},
},
},
},
},
VolumeClaimTemplates: []apiv1.PersistentVolumeClaim{
{
ObjectMeta: metav1.ObjectMeta{Name: "www"},
Spec: apiv1.PersistentVolumeClaimSpec{
AccessModes: []apiv1.PersistentVolumeAccessMode{apiv1.ReadWriteOnce},
Resources: apiv1.ResourceRequirements{
Requests: apiv1.ResourceList{apiv1.ResourceStorage: resource.MustParse("1Gi")},
},
},
},
},
},
}
fmt.Println("creating stateful set", statefulSetName)
_, err := client.Create(context.Background(), statefulset, metav1.CreateOptions{})
if err != nil {
log.Fatal("failed to create stateful set", err)
}
fmt.Println("successfully created stateful set", statefulSetName)
}
If successful, this function creates a StatefulSet
named test-statefulset
.
- First, it gets a
v1.StatefulSetInterface
instance by callingclientset.AppsV1().StatefulSets()
with the default namespace. - Prepares a new
StatefulSet
definition. - Invokes
StatefulSetInterface.Create
to initiateStatefulSet
creation.
Update the main
function - comment out deleteDeployment
function and add the createStatefulSet
function
func main() {
//createDeployment()
//deleteDeployment()
createStatefulSet()
}
Run the program to create a StatefulSet
To run the program:
go run main.go
You should see the following output:
creating stateful set test-statefulset successfully created stateful set test-statefulset
Confirm that the
StatefulSet
was created:kubectl get statefulset/test-statefulset
You should see the following output:
NAME READY AGE test-statefulset 2/2 27s
Verify the
Pod
s created by theStatefulSet
:kubectl get pods -l=app=nginx-app
You should see the following output:
NAME READY STATUS RESTARTS AGE test-statefulset-0 1/1 Running 0 115s test-statefulset-1 1/1 Running 0 112s
Verify the
PersistentVolume
s created by theStatefulSet
(the volume names might differ in your case):kubectl get pv
You should see the following output:
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pvc-4744bb1f-7342-46c3-8324-0f418ee5ca06 1Gi RWO Delete Bound default/www-test-statefulset-1 standard 33m pvc-ba00706e-bfc8-4a03-885b-282b42afe5ad 1Gi RWO Delete Bound default/www-test-statefulset-0 standard 33m
Verify the
PersistentVolumeClaim
s created by theStatefulSet
(the volume names might differ in your case):kubectl get pvc
You should see the following output:
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-test-statefulset-0 Bound pvc-ba00706e-bfc8-4a03-885b-282b42afe5ad 1Gi RWO standard 34m www-test-statefulset-1 Bound pvc-4744bb1f-7342-46c3-8324-0f418ee5ca06 1Gi RWO standard 34m
Add the function to delete a StatefulSet
Add the deleteStatefulSet
function:
func deleteStatefulSet() {
client := clientset.AppsV1().StatefulSets(apiv1.NamespaceDefault)
fmt.Println("deleting stateful set", statefulSetName)
err := client.Delete(context.Background(), statefulSetName, metav1.DeleteOptions{})
if err != nil {
log.Fatal("failed to delete stateful set", err)
}
fmt.Println("successfully deleted stateful set", statefulSetName)
}
Update the main
function - comment out createStatefulSet
function and add the deleteStatefulSet
function
func main() {
//createDeployment()
//deleteDeployment()
//createStatefulSet()
deleteStatefulSet()
}
Run the program to delete an existing StatefulSet
Run the program:
go run main.go
You should see the following output:
deleting stateful set test-statefulset successfully deleted stateful set test-statefulset
Confirm that the
StatefulSet
was deleted:kubectl get statefulset/test-statefulset
You should see the following output:
Error from server (NotFound): statefulsets.apps "test-statefulset" not found
Confirm that
Pod
s created by theStatefulSet
was also deleted:kubectl get pods -l=app=nginx-app
You should see the following output:
No resources found in default namespace.
Conclusion
This article covered how to create and delete a Deployment
and StatefulSet
using the Kubernetes Go client library.
You can also learn more in the following documentation: