How to create CD with GitHub Actions and Kubernetes in your Golang application
- 10 minsAn example to create pipeline to deploy a simple GO application into k3s cluster with Docker image from scratch.
Previous requirements:
- GitHub Account as PHP framework
- Kubernetes cluster in this example I use a lightweight version of Kubernetes with K3s
Simple http application with GO
A simple illustrative example with http server on port 80 with basic handle respond on a main url.
If you already have any application you can go to the next step.
package main
import (
"fmt"
"net/http"
"os"
)
func handler(w http.ResponseWriter, r *http.Request) {
var name, _ = os.Hostname()
fmt.Fprintf(w, "<h1>This request was processed by host: %s</h1>\n", name)
}
func main() {
fmt.Fprintf(os.Stdout, "Web Server started. Listening on 0.0.0.0:80\n")
http.HandleFunc("/", handler)
http.ListenAndServe(":80", nil)
}
You can run the application to ensure works correctly:
go run main.com
If the application its works then we can prepare docker image to “Dokcerize” the application.
Build docker image from scrath
Firstly, we create a file named infrastucture/docker/Dockerfile
to contain all config. We use the alpine
image as a builder to compile de Go binary and then use image from scratch
to copy binary with SSL certificates and define de binary as entrypoint.
This way we get a minimal docker image that contain your binary application than only around 5 MB!
# Use latest Go alpine image as builder
FROM golang:alpine AS build
# Define app as working directory
WORKDIR /app
# Copy all project into docker image
COPY . .
# Compile the Go Application
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /app/bin/demo main.go
# Use minimum image with binary
FROM scratch AS prod
# Copy SSL Certificates from build stage
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
#Copy the application binary from build stage
COPY --from=build /app/bin/demo /app/bin/demo
# Define your binary as entrypoint
ENTRYPOINT ["/app/bin/demo"]
And we can make the build of the image:
docker build -t demo-app:1 . -f ./infrastucture/docker/Dockerfile
-t
: Name and optionally a tag in thename:tag
format-f
: Name of the Dockerfile (Default isPATH/Dockerfile
)
You can found more options on the oficial documentation: https://docs.docker.com/engine/reference/commandline/build/#options
Connect your K3s cluster with Github
For Github Actions can access to your k3s cluster you need import de kubeconfig into secrets actions.
In k3s you can found the kubeconfig
on /etc/rancher/k3s/k3s.yaml
. You kubeconfig will like somehow like this:
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: LS0...
server: https://your-server.com:6443
name: default
contexts:
- context:
cluster: default
user: default
name: default
current-context: default
kind: Config
preferences: {}
users:
- name: default
user:
client-certificate-data: LS0...
client-key-data: LS0...
Then you go to your project from Github and add the secret named KUBE_CONFIG
with the k3s.yaml content.
Project ⮕ Settings ⮕ Secret and Variables ⮕ Actions
Github action for Continuous Delivery (CD)
Finally we come to the long awaited section. We will see how to create a flow with two steps.
Create a file into root of main project: .github/workflow/cd.yaml where we create a workflow triggered on a push/merge on your principal branch, in this case
main branch.
on:
push:
branches: [ "main" ]
First step
In the first step build the docker image and publish on the container registry, in this case use the ghcr
(GitHub Container Registry).
You can found more information about github packages: https://docs.github.com/en/packages/learn-github-packages
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: $
password: $
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64
file: ./infrastructure/docker/Dockerfile
push: true
tags: ghcr.io/$/demo-app:$
Second step
Publish your docker image from registry to k8s cluster.
First of all need a create a token on github with read:package
scope and create secret in json format encoded as base64 that you publish on your k8s secrets.
Let’s Base64 encode it first:
echo -n "username:your_token" | base64
#example of output: dXNlcm5hbWU6eW91cl90b2tlbg==
Change
username
for your GitHub username andyour_token
with the previous generated token
Create a json and encode to Base64 again:
echo -n '{"auths":{"ghcr.io":{"auth":"dXNlcm5hbWU6eW91cl90b2tlbg=="}}}' | base64
#example of output: eyJhdXRocyI6eyJnaGNyLmlvIjp7ImF1dGgiOiJkWE5sY201aGJXVTZlVzkxY2w5MGIydGxiZz09In19fQ==
Create a manifest to deploy your application on kuberntes deployment
, service
, ingress
#infrastructure/kubernetes/demo-app-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
spec:
selector:
matchLabels:
app: demo-app
replicas: 2
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: ghcr.io/path-to-image/demo-app
env:
- name: GIN_MODE
value: "release"
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 3
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 3
#infrastructure/kubernetes/demo-app-service.yml
apiVersion: v1
kind: Service
metadata:
name: demo-app
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
type: NodePort
selector:
app: demo-app
#infrastructure/kubernetes/demo-app-ingress.yml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
name: demo-app
spec:
ingressClassName: nginx
rules:
- host: domain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app
port:
number: 80
Finally, once we have the manifests and secrets, we can create the last step of the workflow.
deploy:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: $
password: $
- name: Kubernetes set context
uses: Azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: $
- name: Deploy
uses: Azure/[email protected]
with:
action: deploy
strategy: basic
imagepullsecrets: |
dockerconfigjson-github-com
manifests: |
./infrastructure/kubernetes/demo-app-deployment.yml
./infrastructure/kubernetes/demo-app-service.yml
./infrastructure/kubernetes/demo-app-ingress.yml
images: ghcr.io/$/demo-app:$
And just commit in your project the complete CD workflow config .github/workflow/cd.yaml
.
name: CD
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: $
password: $
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64
file: ./infrastructure/docker/Dockerfile
push: true
target: prod
tags: ghcr.io/$/demo-app:$
deploy:
needs: [build]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Log in to the Container registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: $
password: $
- name: Kubernetes set context
uses: Azure/k8s-set-context@v3
with:
method: kubeconfig
kubeconfig: $
- name: Deploy
uses: Azure/[email protected]
with:
action: deploy
strategy: basic
imagepullsecrets: |
dockerconfigjson-github-com
manifests: |
./infrastructure/kubernetes/demo-app-deployment.yml
./infrastructure/kubernetes/demo-app-service.yml
./infrastructure/kubernetes/demo-app-ingress.yml
images: ghcr.io/$/demo-app:$
Then you can go to Actions
section in your repository and show all workflows and you can see the summary.
And that’s all just have fun and deploy :-)
You can read the article on Medium