init: go api with CI/CD
All checks were successful
Build & Deploy Go API / build (push) Successful in 33s

This commit is contained in:
joe
2026-07-19 02:43:07 +08:00
committed by Joe
parent 3c2acadc3f
commit 229009bd3c
7 changed files with 243 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
name: Build & Deploy Go API
on:
push:
branches: [main]
env:
REGISTRY: zot-registry.cicd.svc.cluster.local:5000
jobs:
build:
runs-on: self-hosted
steps:
- name: Install build tools
run: apk add --no-cache curl git docker-cli 2>/dev/null || true
- name: Clone + prepare
run: |
WORK=/root/.cache/demo-go-api
mkdir -p $WORK
cd $WORK
[ -d .git ] || git clone http://joe:${GITEA_TOKEN}@192.168.1.212:3000/joe/demo-go-api.git .
git pull
- name: Build & push image
run: |
cd /root/.cache/demo-go-api
SHORT=${GITHUB_SHA::8}
TAG="${REGISTRY}/demo-go-api:${SHORT}"
LATEST="${REGISTRY}/demo-go-api:latest"
docker build --build-arg VERSION=${SHORT} -t "${TAG}" -t "${LATEST}" .
docker push "${TAG}"
docker push "${LATEST}"
echo "✓ Pushed ${TAG}"
env:
GITHUB_SHA: ${{ github.sha }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
- name: Patch ArgoCD
run: |
SHORT=${GITHUB_SHA::8}
if kubectl -n argocd get application demo-go-api &>/dev/null; then
kubectl -n argocd patch application demo-go-api --type merge \
-p "{\"spec\":{\"source\":{\"helm\":{\"parameters\":[{\"name\":\"image.tag\",\"value\":\"${SHORT}\"}]}}}}"
echo "✓ ArgoCD patched"
else
echo "⚠ demo-go-api Application not yet created"
fi
env:
GITHUB_SHA: ${{ github.sha }}

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod ./
RUN go mod download
COPY . .
ARG VERSION=dev
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.VERSION=${VERSION}" -o /out/api .
FROM gcr.io/distroless/static-debian12:nonroot
WORKDIR /app
COPY --from=builder /out/api /app/api
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/app/api"]

9
deploy/helm/Chart.yaml Normal file
View File

@@ -0,0 +1,9 @@
# Minimal Helm chart for any service
# Place at: <repo>/deploy/helm/
apiVersion: v2
name: demo-go-api
description: Standard Helm chart for k3s services
type: application
version: 0.1.0
appVersion: "1.0.0"

View File

@@ -0,0 +1,85 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
labels:
app: {{ .Chart.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Chart.Name }}
template:
metadata:
labels:
app: {{ .Chart.Name }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.registry }}/{{ .Values.image.name }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
{{- if .Values.probes.liveness.enabled }}
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: {{ .Values.probes.liveness.port }}
initialDelaySeconds: 30
periodSeconds: 10
{{- end }}
{{- if .Values.probes.readiness.enabled }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: {{ .Values.probes.readiness.port }}
initialDelaySeconds: 5
periodSeconds: 5
{{- end }}
env:
{{- toYaml .Values.env | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
---
apiVersion: v1
kind: Service
metadata:
name: {{ .Chart.Name }}
spec:
type: {{ .Values.service.type }}
selector:
app: {{ .Chart.Name }}
ports:
- port: {{ .Values.service.port }}
targetPort: http
protocol: TCP
name: http
{{- if .Values.ingress.enabled }}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Chart.Name }}
annotations:
{{- if .Values.ingress.className }}
kubernetes.io/ingress.class: {{ .Values.ingress.className }}
{{- end }}
spec:
{{- if .Values.ingress.className }}
ingressClassName: {{ .Values.ingress.className }}
{{- end }}
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: {{ .Values.ingress.path | default "/" }}
pathType: Prefix
backend:
service:
name: {{ .Chart.Name }}
port:
number: {{ .Values.service.port }}
{{- end }}

38
deploy/helm/values.yaml Normal file
View File

@@ -0,0 +1,38 @@
# Default values
image:
registry: 127.0.0.1:5000
name: demo-go-api
tag: latest
pullPolicy: Always
replicaCount: 2
service:
type: ClusterIP
port: 8080
ingress:
enabled: false
className: traefik
host: demo-go-api.local
path: /
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
env: []
probes:
liveness:
enabled: true
path: /healthz
port: 8080
readiness:
enabled: true
path: /healthz
port: 8080

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module github.com/joe/demo-go-api
go 1.23

44
main.go Normal file
View File

@@ -0,0 +1,44 @@
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
)
type Response struct {
Service string `json:"service"`
Message string `json:"message"`
Time string `json:"time"`
Hostname string `json:"hostname"`
Version string `json:"version"`
}
func main() {
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
hostname, _ := os.Hostname()
resp := Response{
Service: "demo-go-api",
Message: "Hello from Go API!",
Time: time.Now().Format(time.RFC3339),
Hostname: hostname,
Version: os.Getenv("VERSION"),
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
})
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
log.Printf("Listening on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}