Skip to main content

Go Gitlab CI Pipeline

Shenzhen, China

Github Repository

Hello Go

Initializing the Project

mkdir hello-go && cd hello-go

Create a web hello world and a test function that can verify our application is operational:

main.go

package main

import (
"log"
"net/http"

"github.com/gorilla/mux"
)

var port = "6969"

func main() {
log.Fatal(http.ListenAndServe(":"+port, router()))
}

func router() http.Handler {
r := mux.NewRouter()
r.Path("/hi").Methods(http.MethodGet).HandlerFunc(greet)
return r
}

func greet(w http.ResponseWriter, req *http.Request) {
_, _ = w.Write([]byte("Go la la!"))
}

main-test.go

package main

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

func TestRouter(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "/hi", nil)
router().ServeHTTP(w, req)

expected := "Go la la!"
actual := w.Body.String()
if expected != actual {
t.Fatalf("Expected %s but got %s", expected, actual)
}
}

And initialize the project with:

go mod init hello-go
go: creating new go.mod: module hello-go
go: to add module requirements and sums:
go mod tidy

This will generate our Go modules file:

module hello-go

go 1.19

require github.com/gorilla/mux v1.8.0

Now we can use the tidy command to pull the required dependencies:

go mod tidy
go: finding module for package github.com/gorilla/mux
go: downloading github.com/gorilla/mux v1.8.0
go: found github.com/gorilla/mux in github.com/gorilla/mux v1.8.0

Verify the Project

Run the hello world application with:

go run ./main.go

And verify that the hello world string is served as expected:

curl http://localhost:6969/hi
Go la la!

And now to using our automated test:

go test ./...
ok hello-go 0.003s

Looking good :thumbsup:

Test Build

We can try a build and verify that the compiled application is working:

go build -o hi .
./hi
curl http://localhost:6969/hi
Go la la!

Dockerize

Now we can use Docker to first build the hello world application inside a (big - 352MB) golang:alpine Docker container. And then copy the build binary into a (tiny 5.54MB) Alpine container ready for deployment:

FROM golang:alpine as build

WORKDIR /build

COPY . .

RUN go mod tidy

RUN go build -o hi

FROM alpine:latest

COPY --from=build /build/hi /usr/local/bin/hi

ENTRYPOINT ["/usr/local/bin/hi"]

Note that both source images are based on Alpine linux to reduce the container size. You cannot use. e.g. the default golang:latest container (non-Alpine) to build the binary and then run it inside an Alpine container. (How does that affect cross-compiling?)

Running the Build

docker build -t hi-there .

Sending build context to Docker daemon 6.81MB
Step 1/8 : FROM golang:alpine as build
---> 6e31dcd72d8f
Step 2/8 : WORKDIR /go/src/app
---> Running in 793c410f2896
Removing intermediate container 793c410f2896
---> 4b8c2620281a
Step 3/8 : COPY . .
---> b244b9819dba
Step 4/8 : RUN go mod tidy
---> Running in 287c8695c309
go: downloading github.com/gorilla/mux v1.8.0
Removing intermediate container 287c8695c309
---> 8ba74df6c6e1
Step 5/8 : RUN go build -o hi
---> Running in d910102a1d38
Removing intermediate container d910102a1d38
---> d72a114948bc
Step 6/8 : FROM alpine:latest
---> 9c6f07244728
Step 7/8 : COPY --from=build /go/src/app/hi /usr/local/bin/hi
---> 86f8a7da8842
Step 8/8 : ENTRYPOINT ["/usr/local/bin/hi"]
---> Running in bacdb35f27ec
Removing intermediate container bacdb35f27ec
---> b4a259e2f0df
Successfully built b4a259e2f0df
Successfully tagged hi-there:latest

This generated two docker images:

docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hi-there latest b4a259e2f0df 20 seconds ago 12.3MB
<none> <none> d72a114948bc 22 seconds ago 367MB

The first one is our application image and the second one is the build image that can be removed - docker rmi d72a114948bc.

We can test the application by running:

docker run -ti -p 6969:6969 --name hi-there  hi-there /bin/ash /usr/local/bin/hi

And, again, verify that everything is running:

docker ps

IMAGE COMMAND PORTS NAMES
hi-there "/usr/local/bin/hi /…" 0.0.0.0:6969->6969/tcp hi-there
curl localhost:6969/hi
Go la la!

Gitlab CI Pipeline

The project now looks like:

hello-go
├── Dockerfile
├── go.mod
├── go.sum
├── hi
├── main.go
├── main_test.go
└── .gitlab-ci.yml

The following commands can be used to install all dependencies, to run the application:

go get -v -d ./...
go test -v ./...
go run src/main.go

Now everything is set and tested we need to write a .gitlab-ci.yml file to configure a build pipeline:

image: golang:latest

variables:
REPO: my.gitlab.com
GROUP: server_management
PROJECT: go-hello

stages:
- test
- build

before_script:
- mkdir -p $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds
- cp -r $CI_PROJECT_DIR $GOPATH/src/$REPO/$GROUP/$PROJECT
- ln -s $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds/$GROUP
- go get -v -d ./...

unit_tests:
stage: test
script:
- go test -v ./...

build:
stage: build
script:
- go build -v -o hi
- ls -la
- pwd
only:
- main
artifacts:
paths:
- /builds/server_management/hello-go/hi
expire_in: 1 hour

Register a Runner

Gitlab needs a minion process to run pipeline. The following commands will install the runner on a LINUX server.

Download and install binary

# Download the binary for your system
curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

# Give it permission to execute
chmod +x /usr/local/bin/gitlab-runner

# Create a GitLab Runner user
useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

# Install and run as a service
gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
gitlab-runner start

Command to register runner

The Registration Token can be found in the project settings under CI/CD:

Go Gitlab CI Pipeline

gitlab-runner register --url GITLAB_URL --registration-token REGISTRATION_TOKEN

Registering runner... succeeded
Enter an executor: docker
Enter the default Docker image: golang:alpine
Runner registered successfully

Configuration is stored in /etc/gitlab-runner/config.toml.

Run the Pipeline

...

$ mkdir -p $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds
$ cp -r $CI_PROJECT_DIR $GOPATH/src/$REPO/$GROUP/$PROJECT
$ ln -s $GOPATH/src/$REPO/$GROUP $GOPATH/src/_/builds/$GROUP
$ go get -v -d ./...
go: downloading github.com/gorilla/mux v1.8.0
$ go build -v -o hi
github.com/gorilla/mux
hello-go
Uploading artifacts for successful job
00:02
Uploading artifacts...
/builds/server_management/hello-go/hi: found 1 matching files and directories
Uploading artifacts as "archive" to coordinator... 201 Created id=14602 responseStatus=201 Created token=FZzQWvUQ
Cleaning up project directory and file based variables
00:00
Job succeeded

Download the Artifacts

The build binary can be downloaded from:

https://my.gitlab.com/server_management/hello-go/-/jobs/artifacts/main/browse?job=build

Go Gitlab CI Pipeline