Go app on Kubernetes from scrach
Welcome
I like GitHub Actions, I like Kubernetes and I want to learn more about Helm. So maybe I should join these tools and make a smooth pipeline? Why not? Also, I switched to Fedora, and that's a great moment to checkout Podman in action. No time to wait, let's go.
Tools used in this episode
- GitHub Action
- Podman
- Kubernetes
- Terraform
- Helm
- GCP
- A bit of Golang :)
Build the app
The first step is building a small app. I decided to use Golang because it's an awesome language for microservices and testing is clear.
Create a directory for app and infra part
1mkdir -pv app infra
Go to the app directory and create
main.go
1 2 3 4 5 6 7
package main import "fmt" func main() { fmt.Println("Hello World!") }
Init go mod
1go mod init 3sky/k8s-app
Write code
I decided to use Echo framework, I like it, it's fast and logger is easy to use. \
App has two endpoint:
/hello
- which returnHello World!
/status
- which retrun app status =OK
1package main 2 3import ( 4 "net/http" 5 "time" 6 "github.com/labstack/echo/v4" 7 "github.com/labstack/echo/v4/middleware" 8) 9// Greetings ... 10type Greetings struct { 11 Greet string `json:"greet"` 12 Date time.Time `json:"date"` 13} 14// Status ... 15type Status struct { 16 Status string `json:"status"` 17} 18func main() { 19 // Echo instance 20 e := echo.New() 21 // Middleware 22 e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ 23 Format: "method=${method}, uri=${uri}, status=${status}\n", 24 })) 25 e.Use(middleware.Recover()) 26 // Routes 27 e.GET("/hello", HelloHandler) 28 e.GET("/status", StatusHandler) 29 // Start server 30 e.Logger.Fatal(e.Start(":1323")) 31} 32// HelloHandler ... 33func HelloHandler(c echo.Context) error { 34 return c.JSON(http.StatusOK, &Greetings{Greet: "Hello, World!", Date: time.Now()}) 35} 36// StatusHandler ... 37func StatusHandler(c echo.Context) error { 38 return c.JSON(http.StatusOK, &Status{Status: "OK"}) 39}
Download dependences
1go mod tidy
Run the code
1go run main.go
Add some basic tests
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
package main import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/labstack/echo/v4" ) var ( g = Greetings{} s = Status{} ) func TestGreetings(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) HelloHandler(c) if rec.Code != 200 { t.Errorf("Expected status code is %d, but it was %d instead.", http.StatusOK, rec.Code) } json.NewDecoder(rec.Body).Decode(&g) if g.Greet != "Hello, World!" { t.Errorf("Expected value is \"Hello, World!\", but it was %s instead.", g.Greet) } } func TestStatus(t *testing.T) { e := echo.New() req := httptest.NewRequest(http.MethodGet, "/status", nil) rec := httptest.NewRecorder() c := e.NewContext(req, rec) StatusHandler(c) if rec.Code != 200 { t.Errorf("Expected status code is %d, but it was %d instead.", http.StatusOK, rec.Code) } json.NewDecoder(rec.Body).Decode(&s) if s.Status != "OK" { t.Errorf("Expected value is \"OK\", but it was %s instead.", s.Status) } }
Run tests
1go test ./...
Containerization with Podman
We need to pack out an awesome app. To do that I decided to use Podman. What Podman is? It is a daemonless container engine for developing, managing, and running OCI Containers on your Linux System. Unfortunately, I prefer creating Dockerfile in Docker's way, Buildah is not for me at least now.
Create contianer
Create Dockerfile
1# Dockerfile 2FROM golang:alpine as builder 3RUN apk add --no-cache git gcc libc-dev 4WORKDIR /build/app 5# Get depedences 6COPY go.mod ./ 7RUN go mod download 8# Run Testss 9COPY . ./ 10RUN go test -v ./... 11# Build app 12RUN go build -o myapp 13FROM alpine 14COPY --from=builder /build/app/myapp ./myapp 15EXPOSE 1323 16CMD ["./myapp"]
Build an image
1podman build -t k8s-app .
Run image
1podman run -d -p 8080:1323 k8s-app:latest
Run basic
curl
's test1curl -s localhost:8080/status | jq . 2curl -s localhost:8080 | jq .
Configure GCP
OK, we have working app now we need to create our Kubernetes cluster for our deployment.
Working with GCP
Auth into GCP
1gcloud auth login
Create a new project
1gcloud projects create [PROJECT_ID] --enable-cloud-apis 2 3# --enable-cloud-apis 4# enable cloudapis.googleapis.com during creation 5# example 6# gcloud projects create calcium-hobgoblins --enable-cloud-apis
Check existing projects
1gcloud projects list 2PROJECT_ID NAME PROJECT_NUMBER 3calcium-hobgoblins calcium-hobgoblins xxxx
Set
gcloud
project1gcloud config set project calcium-hobgoblins
Create a service account and add necessary permission
1gcloud iam service-accounts create calcium-hobgoblins-user \ 2--description "Service user for GKE and GitHub Action" \ 3--display-name "calcium-hobgoblins-user" 4 5gcloud projects add-iam-policy-binding calcium-hobgoblins --member \ 6serviceAccount:calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com \ 7--role roles/compute.admin 8 9gcloud projects add-iam-policy-binding calcium-hobgoblins --member \ 10serviceAccount:calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com \ 11--role roles/storage.admin 12 13gcloud projects add-iam-policy-binding creeping-hobgoblins --member \ 14serviceAccount:calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com \ 15--role roles/container.admin 16 17gcloud projects add-iam-policy-binding calcium-hobgoblins --member \ 18serviceAccount:calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com \ 19--role roles/iam.serviceAccountUser
List permission calcium-hobgoblins
1gcloud projects get-iam-policy calcium-hobgoblins \ 2--flatten="bindings[].members" \ 3--format='table(bindings.role)' \ 4--filter="bindings.members:calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com"
Push initial image to container registry
After setting up cloud project we have finally access to the container registry.
Auth and Push
Authenticate container registry
1gcloud auth activate-service-account \ 2calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com \ 3--key-file=/home/kuba/.gcp/calcium-hobgoblins.json 4 5gcloud auth print-access-token | podman login \ 6-u oauth2accesstoken \ 7--password-stdin https://gcr.io
Push image into gcr.io
1podman push localhostk8s-app:latest docker://gcr.io/calcium-hobgoblins/k8s-app:0.0.1
Provide Kubernetes Cluster
After setting up our GCP's project we need to provision out K8S cluster.
Create auth file
1mkdir -pv ~/.gcp 2cloud iam service-accounts keys create ~/.gcp/calcium-hobgoblins.json \ 3--iam-account calcium-hobgoblins-user@calcium-hobgoblins.iam.gserviceaccount.com
Create a basic directory structure
1cd ../infra 2mkdir -pv DEV Module/GKE
Terraform directory structure looks like that:
1. 2├── DEV 3│ ├── main.tf 4│ └── variables.tf 5└── Module 6 └── GKE 7 ├── main.tf 8 └── variables.tf
Init Terrafrom
1cd DEV 2terraform init
Permission are important
If we forget about
devstorage
out cluster will have a problem with pulling images...1oauth_scopes = [ 2 "https://www.googleapis.com/auth/logging.write", 3 "https://www.googleapis.com/auth/monitoring", 4 "https://www.googleapis.com/auth/devstorage.read_only" 5]
Terraform apply
1terraform apply -var="path=~/.gcp/calcium-hobgoblins.json"
Config
kubectl
1export cls_name=my-gke-cluster 2export cls_zone=europe-west3-a 3gcloud container clusters list 4gcloud container clusters get-credentials cls_name --zone cls_zone 5kubectl get node
Prepare Helm release
When we have a working cluster, we can prepare helm chart. Also, it's a good time to install an ingress controller.
Init example helm chart
1cd ../.. 2mkdir helm-chart 3cd helm-chart 4helm create k8s-app
Install Ingress with Helm(nginx)
1helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2helm install release ingress-nginx/ingress-nginx
Add GitHub Action Pipeline
As an easy and great CI tool I decided to use GitHub Action again.
Add two files
1mkdir -pv .github/workflows 2touch .github/workflows/no-release.yml 3touch .github/workflows/release.yml
Add content to
no-release
fileThis file will execute every time when code will be pushed to the repository.
Add content to
release
fileThis file will execute only when pushed code will be tagged with
v*
expression.Set GH Secrets
PROJECT_ID
- it's project - calcium-hobgoblinsGCP_SA_KEY
- auth file in base641cat ~/.gcp/calcium-hobgoblins.json | base64
Push some code into the repo
1git push origin master 2git push origin v.0.0.1
Check the status of pods
1kubectl get pods 2kubectl describe pod <pod-name> 3helm list release-k8s-app
Summary
As you can see there is no source file for terraform and helm.
I decided for that move because the post is long enough even without it :)
What else? I like Podman it just works without root permission on the host.
I still have some problems with Buildah, it's a bit uncomfortable for me.
Maybe in the future, or after another attempt.
Setting K8S cluster is easy with
Terraform, but If we are planning production deployment all factors become
more complicated.
Helm also looks like a nice tool in case a lot of similar
deployment, also tracking release history is a cool feature. Unfortunately, it's
not a magic tool and doesn't resolve all our CI/CD problems.
All code you can find here