Learn How to Setup a CI/CD Pipeline from Scratch
Tags: tutorial, devops, go, kubernetes
Published: 2023-03-01 05:34:03

**In this tutorial, we will take an example of a Go application and setup a CI/CD pipeline.** 

[Go](https://go.dev/) is becoming increasingly popular amongst developers for its ability to simplify and secure the building of modern applications. The language was created by Google and has gained traction due to its open-source nature and ability to write programs in the Go language. Go also provides users with the freedom to build their own front-end websites and applications, as well as making it easy to develop, maintain and use. Enterprises can rely on Go to help build and scale cloud computing systems while enjoying its powerful concurrency features. Furthermore, Go offers high performance without utilizing too many resources.

Today, we will create a simple Go application and set up a CI/CD pipeline for the same. Let's Go!

## Prerequisites
- Create a [free SingleStore account](https://www.singlestore.com/cloud-trial/?utm_campaign=singlestore-for-ai&utm_medium=referral&utm_source=pavan&utm_term=cicd). For this tutorial, we'll be using SingleStore as our database solution. SingleStore is a high-performance, in-memory database that supports both SQL and NoSQL data models.  

- Create a free [Harness cloud account](https://app.harness.io/auth/#/signup/?utm_source=internal&utm_medium=social&utm_campaign=community&utm_content=pavan_go_cicd_article&utm_term=get-started) to setup CI/CD
- Download & install Go quickly from [here](https://go.dev/doc/install)
- Kubernetes cluster access from any cloud provider to deploy our application (you can also use [Minikube](https://minikube.sigs.k8s.io/docs/start/) or [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) to create a single node cluster).
- Docker, preferably [Docker Desktop](https://www.docker.com/products/docker-desktop/)

## Tutorial
The example repository is accessible [here](https://github.com/pavanbelagatti/Golang-CICD-Tutorial); feel free to fork it or just follow along. I will not go into many details about the application code itself. It is a sample “Hello World” app that prints the text “Hello World” on the local host 8080. 

Here is the code for the `main.go` file:

```
package main

import (
    "fmt"
    "log"
    "net/http"
)

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Home Page")
}

func wsEndpoint(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World")
}

func setupRoutes() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/ws", wsEndpoint)
}

func main() {
    fmt.Println("Hello World")
    setupRoutes()
    log.Fatal(http.ListenAndServe(":8080", nil))
}
```
The application also has a test file `main_test.go` with simple test case. 

```
package main

import (
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHomePage(t *testing.T) {
    req, err := http.NewRequest("GET", "/", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(homePage)
    handler.ServeHTTP(rr, req)
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }
    if rr.Body.String() != "Home Page" {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), "Home Page")
    }
}

func TestWsEndpoint(t *testing.T) {
    req, err := http.NewRequest("GET", "/ws", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr := httptest.NewRecorder()
    handler := http.HandlerFunc(wsEndpoint)
    handler.ServeHTTP(rr, req)
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }
    if rr.Body.String() != "Hello World" {
        t.Errorf("handler returned unexpected body: got %v want %v",
            rr.Body.String(), "Hello World")
    }
}
```

The Dockerfile you see in the repo will be used to build and push our application as an image to the Docker Hub. 

```
FROM golang:1.16.4-buster AS builder

ARG VERSION=dev

WORKDIR /go/src/app
COPY main.go .
RUN go build -o main -ldflags=-X=main.version=${VERSION} main.go 

FROM debian:buster-slim
COPY --from=builder /go/src/app/main /go/bin/main
ENV PATH="/go/bin:${PATH}"
CMD ["main"]
```

The next thing is we will build the image and push it to the Docker Hub using the command,

```
docker buildx build --platform=linux/arm64 --platform=linux/amd64 -t docker.io/<docker hub username>/<image name>:<tag> --push -f ./Dockerfile .
```

Once the build and push are successful, you can confirm it by going to your Docker Hub account.

![Golang image on DockerHub](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f5n5905obg0tma6nzmsc.png)

We will be deploying our application on a Kubernetes cluster.
You can see the `deployment.yaml` and `service.yaml` files in the [forked repo](https://github.com/pavanbelagatti/Golang-CICD-Tutorial), which define the deployment and service to help us deploy and expose our application. At this point, make sure your Kubernetes cluster is up and running.

Our deployment.yaml file is shown below

```
apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-app-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: go-app
  template:
    metadata:
      labels:
        app: go-app
    spec:
      containers:
      - name: go-app
        image: pavansa/golang-hello-world:latest
        ports:
        - containerPort: 8080
        env:
        - name: PORT
          value: "8080"
```
The service.yaml file is shown below,

```
apiVersion: v1
kind: Service
metadata:
  name: go-app-service
spec:
  selector:
    app: go-app
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 8080
  type: LoadBalancer
```

It is time to set up a Harness account to do CI/CD. Harness is a modern CI/CD platform with AI/ML capabilities. 

Create a [free Harness account](https://app.harness.io/auth/#/signup/?utm_source=internal&utm_medium=social&utm_campaign=community&utm_content=pavan_go_cicd_article&utm_term=get-started) and your first project. Once you sign-up at Harness, you will be presented with the new CI/CD experience and capabilities.

Add the required connectors, such as [Harness Delegate](https://developer.harness.io/docs/platform/delegates/get-started-with-delegates/delegates-overview/), your GitHub repo, Docker Hub account and secrets. Delegate in Harness is a service/software you need to install/run on the target cluster [Kubernetes cluster in our case] to connect your artifacts, infrastructure, collaboration, verification and other providers with the Harness Manager. _When you set up Harness for the first time, you install a Harness Delegate_.

## Continuous Integration

After signing in at Harness, it will first ask you to create a project.

![start with the Project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/80mu06zxt7k0wd3ynpl3.png)

Invite collaborators to the project if you want.
![Invite collaborators](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyy16j0vir36rpeyln3j.png)

Select the Continuous Integration module.
![Continuous Integration module](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8jicnfdrk4wbq0r2agy2.png)

Create your first pipeline.
![first pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tewkm3r4pg65gp97z0ok.png)

Connect with your source control management like GitHub, where the application code is present.
![scm connect](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/561e6biumy8dbt9nvqrj.png)

Next, configure your pipeline with the proper language of the project.
![configure pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cy027oenjpjh5m9x2vw6.png)

When you save and continue, you see the below default setup in the pipeline studio.
![pipeline studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/29r36bcu6usj3h4enfx7.png)

When you click on the 'Go Build App' under execution, you will see the below setup details.
![go build app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a9imr773iiu0ue6sl12.png)

![default settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hez87xuuz3aq2qd16e04.png)

Let's modify the previous/above step, name it as 'Test Go App' add the following code in the command tab, and save and run the pipeline. 

![modify pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vm4tomgexfkmvuzjbs8r.png)

![run pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9a0loj3znhvqkqqoacg3.png)

You should see a successful output:)
![successful output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s2fzk2bq0xbhk7arbycn.png)

We successfully created a CI pipeline for our application where the build and testing of the code happens. Let's extend the idea of deploying this error-free application code our target environment, i.e Kubernetes.

## Continuous Delivery and Deployment

It is time to deploy our Go application, create the deployment stage. 
![app deploy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3sdkhabpzd8h8xiqxoj.png)

Add a name to your stage, select the deployment type as 'Kubernetes', and click 'Set up Stage'.
![deploy details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kecwxnc2hcckiei3ci47.png)

This is what you should be seeing after creating the deploy stage.
![service studio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdix0mm5olgcw9au50d9.png)

We need to create a service. Hence, click on 'add service'. In the next step, we need to add a name to our service and manifest details.
![service details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cj0cix975t4bgoqn8nur.png)

Click on 'Add manifest' to add the details.
![manifest details add](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3yb388vla0225ohmly9.png)

Then select K8S manifest and continue.
![k8s manifest](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ighostztd1317ioy25dk.png)

![k8s type mani](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iyectfwne78aydb0xtgc.png)

Specify the K8S manifest store. We know that our manifest files are present in GitHub. Hence select GitHub.
![manifest store](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k1wz956digcw7b9qncwc.png)

Add a new GitHub connector to connect your manifest files.
![github connector files](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/es85zd0zka12s3rfcgye.png)

Specify all the details step by step.
![source details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b18w7blm3m54w3yn3nfd.png)

Add credentials through inbuilt secrets.
![cred secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6ot2kpd5itmq3ej89n3.png)

Connect with your delegate.
![connect delegate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm8sbgam776sgd3me7x6.png)

Make sure the connection of your manifest is successful with your delegate.
![successful delegate connection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d0iswnvfi1huldr0u0qj.png)

Now, add the manifest details from your GitHub repo.
![mani details from repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32fnwxs2j2k6nswoletf.png)
Save everything and continue.
You should see the service with manifest details.

![service with manifest details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ktjns5w2jkdlvut6gqbd.png)

Your pipeline studio will look like this, with your added service. 
![service added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a78ofoluo5usdy6etgfr.png)

Add a new environment, save and continue.
![new environment](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mwr3r3r4c0xoy6gezns.png)

Similarly, add new infrastructure. Select infrastructure type as 'Kubernetes' and add the cluster details.
![infra kube type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1763vhiukibbwelcqg3v.png)

![cluster details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2dx12lx74vukfg0v5bqw.png)

Save and continue.

In the next step, you need to select the deployment strategy type. We are selecting 'Rolling' as our deployment strategy.
![deployment strategy](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j136ijj431e1pu1n71a9.png)

We are all done and this is how our CD pipeline looks like.
![CD pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/owkklxsd0auop7nylhil.png)

Now, save everything and run the pipeline.
![run CICD pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sqhfpolo2f33jzhewlo7.png) 

You should see a successful pipeline execution starting from CI and then CD step by step. 
![CICD successful](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/euaeh3gcmbha2menwits.png)

## Automate CI/CD

The last step is to automate our CI/CD pipeline by creating Triggers. Let's do it.

In the pipeline studio, you can see the 'Triggers' tab.
![Triggers tab](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8m216cjw14h474o8825o.png)

Click the Triggers tab and create a new trigger.
![new trigger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6o0qmmcewano5tnntfyv.png)

Add the GitHub trigger, whenever someone pushes a new code to the main branch, the pipeline should trigger automatically. 
![GitHub trigger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6qug6v07kbiklyz79yan.png)

![trigger details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b8ugp1tsc1tssgolddr.png)
![trigger added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vj0t6yr0mg0gjfmtizgv.png)
![more trigger details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1q3qq3faxr6jqp9tkxat.png)

Save everything and create the trigger.

You should see the created trigger under the Trigger tab.

Now, let's confirm if our CI/CD is automated and working properly. Add a readme file to our GitHub repo and see if it triggers our pipeline.
![updated readme file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/46hmkft6h1wvoh3288ec.png)

You see, the pipeline triggered when a new code commit happened. 
![automated pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rn2v9xr41mkteyee73sa.png)

We have successfully automated the CI/CD process for our Go application using Harness. 

Also, checkout my other articles on continuous integration and deployment.
{% embed https://dev.to/pavanbelagatti/a-step-by-step-guide-to-continuous-integration-for-your-nodejs-application-3bhc %}

{% embed https://dev.to/pavanbelagatti/deploying-an-application-on-kubernetes-a-complete-guide-1cj6 %}