Skip to content

Commit b97c6dd

Browse files
author
Pattarapon (Petong) Theanthong
committed
feat: initial commit with full monorepo structure
0 parents  commit b97c6dd

11 files changed

Lines changed: 282 additions & 0 deletions

File tree

.github/workflows/app-ci.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Build and Push App
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
paths:
7+
- "app/**"
8+
- ".github/workflows/app-ci.yml"
9+
10+
permissions:
11+
contents: write
12+
13+
env:
14+
IMAGE_NAME: pazzii/go-sample-app
15+
16+
jobs:
17+
build-and-push:
18+
runs-on: ubuntu-24.04
19+
steps:
20+
- name: Checkout code
21+
uses: actions/checkout@v6
22+
23+
- name: Set up Docker Buildx
24+
uses: docker/setup-buildx-action@v3
25+
26+
- name: Login to Docker Hub
27+
uses: docker/login-action@v3
28+
with:
29+
username: ${{ secrets.DOCKERHUB_USERNAME }}
30+
password: ${{ secrets.DOCKERHUB_TOKEN }}
31+
32+
- name: Extract metadata (tags, labels)
33+
id: meta
34+
uses: docker/metadata-action@v5
35+
with:
36+
images: ${{ env.IMAGE_NAME }}
37+
tags: |
38+
type=sha,format=long
39+
type=raw,value=latest
40+
41+
- name: Build and push Docker image
42+
uses: docker/build-push-action@v6
43+
with:
44+
context: ./app
45+
push: true
46+
tags: ${{ steps.meta.outputs.tags }}
47+
labels: ${{ steps.meta.outputs.labels }}
48+
cache-from: type=gha
49+
cache-to: type=gha,mode=max
50+
51+
- name: Update Kustomize Image Tag
52+
run: |
53+
cd k8s/overlays/production
54+
kustomize edit set image ${{ env.IMAGE_NAME }}=${{ env.IMAGE_NAME }}:sha-${{ github.sha }}
55+
56+
git config --global user.name 'GitHub Actions'
57+
git config --global user.email 'actions@github.com'
58+
git add kustomization.yaml
59+
git commit -m "chore: update image tag to sha-${{ github.sha }}"
60+
git push

.gitignore

Whitespace-only changes.

app/Dockerfile

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# syntax=docker/dockerfile:1
2+
3+
# ==============================================================================
4+
# Stage 1: Builder
5+
# Description: Compiles the Go application into a statically linked binary.
6+
# ==============================================================================
7+
FROM golang:1.21.4-alpine3.18 AS builder
8+
9+
# Set the working directory inside the build container.
10+
WORKDIR /build
11+
12+
# Copy dependency definition files first to leverage Docker layer caching.
13+
# This ensures 'go mod download' is only re-run if dependencies change.
14+
COPY go.mod ./
15+
16+
# Download dependencies.
17+
# Optimization: Use '--mount=type=cache' to persist the module cache between builds,
18+
# significantly speeding up CI/CD pipelines.
19+
RUN --mount=type=cache,target=/go/pkg/mod \
20+
go mod download
21+
22+
# Copy the entire source code into the container.
23+
COPY . .
24+
25+
# Build the application binary.
26+
# Compilation Flags:
27+
# - CGO_ENABLED=0 : Ensures a statically linked binary (no dependency on C libraries).
28+
# - GOOS=linux : Explicitly targets Linux environment.
29+
# - -ldflags="-w -s": Strips debug information (DWARF) and symbol tables to reduce image size.
30+
# Optimization: Re-uses the Go build cache.
31+
RUN --mount=type=cache,target=/root/.cache/go-build \
32+
--mount=type=cache,target=/go/pkg/mod \
33+
CGO_ENABLED=0 GOOS=linux go build \
34+
-ldflags="-w -s" \
35+
-o app .
36+
37+
# ==============================================================================
38+
# Stage 2: Runner
39+
# Description: Creates a minimal, secure production image using Distroless.
40+
# Security: Contains no shell, package manager, or unnecessary tools.
41+
# ==============================================================================
42+
FROM gcr.io/distroless/static-debian12:nonroot as release
43+
44+
# Set the working directory to root.
45+
WORKDIR /
46+
47+
# Retrieve the compiled binary from the builder stage.
48+
COPY --from=builder /build/app /app
49+
50+
# Document the port that the application listens on.
51+
EXPOSE 8080
52+
53+
# Security Best Practice: Run as a non-root user.
54+
# Using the numeric ID 65532 (which corresponds to 'nonroot' in Distroless)
55+
# is preferred for strict Kubernetes Pod Security Standards (PSS).
56+
USER 65532:65532
57+
58+
# Define the immutable entrypoint for the container.
59+
ENTRYPOINT ["/app"]

app/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module hello-api
2+
3+
go 1.21.4

app/main.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
)
10+
11+
func main() {
12+
http.HandleFunc("/", getHello)
13+
err := http.ListenAndServe(":8080", nil)
14+
if errors.Is(err, http.ErrServerClosed) {
15+
fmt.Printf("server closed\n")
16+
} else if err != nil {
17+
fmt.Printf("error starting server: %s\n", err)
18+
os.Exit(1)
19+
}
20+
}
21+
22+
func getHello(w http.ResponseWriter, r *http.Request) {
23+
name := r.URL.Query().Get("name")
24+
io.WriteString(w, "Hello "+name+"\n")
25+
}

k8s/base/analysis.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: argoproj.io/v1alpha1
2+
kind: AnalysisTemplate
3+
metadata:
4+
name: smoke-test
5+
spec:
6+
metrics:
7+
- name: web-check
8+
provider:
9+
job:
10+
spec:
11+
template:
12+
spec:
13+
containers:
14+
- name: curl-check
15+
image: curlimages/curl
16+
command: ["curl", "-f", "http://go-sample-app-svc:80"]
17+
restartPolicy: Never
18+
backoffLimit: 2

k8s/base/kustomization.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
apiVersion: kustomize.config.k8s.io/v1beta1
2+
kind: Kustomization
3+
4+
resources:
5+
- rollout.yaml
6+
- service.yaml
7+
- analysis.yaml
8+
9+
configurations:
10+
- kustomizeconfig.yaml

k8s/base/kustomizeconfig.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
nameReference:
2+
- kind: ConfigMap
3+
version: v1
4+
fieldSpecs:
5+
- path: spec/template/spec/containers/envFrom/configMapRef/name
6+
kind: Rollout
7+
- path: spec/template/spec/volumes/configMap/name
8+
kind: Rollout

k8s/base/rollout.yaml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
apiVersion: argoproj.io/v1alpha1
2+
kind: Rollout
3+
metadata:
4+
name: go-sample-app
5+
spec:
6+
replicas: 1
7+
revisionHistoryLimit: 3
8+
selector:
9+
matchLabels:
10+
app: go-sample-app
11+
strategy:
12+
canary:
13+
analysis:
14+
templates:
15+
- templateName: smoke-test
16+
steps:
17+
- setWeight: 20
18+
- pause: { duration: 30s }
19+
- setWeight: 50
20+
- pause: { duration: 30s }
21+
- setWeight: 100
22+
template:
23+
metadata:
24+
labels:
25+
app: go-sample-app
26+
spec:
27+
affinity:
28+
podAntiAffinity:
29+
preferredDuringSchedulingIgnoredDuringExecution:
30+
- weight: 100
31+
podAffinityTerm:
32+
labelSelector:
33+
matchExpressions:
34+
- key: app
35+
operator: In
36+
values:
37+
- go-sample-app
38+
topologyKey: kubernetes.io/hostname
39+
containers:
40+
- name: main
41+
image: pazzii/go-sample-app:latest
42+
imagePullPolicy: Always
43+
ports:
44+
- containerPort: 8080
45+
envFrom:
46+
- configMapRef:
47+
name: app-config
48+
livenessProbe:
49+
httpGet:
50+
path: /
51+
port: 8080
52+
initialDelaySeconds: 5
53+
periodSeconds: 10
54+
readinessProbe:
55+
httpGet:
56+
path: /
57+
port: 8080
58+
initialDelaySeconds: 5
59+
periodSeconds: 10
60+
securityContext:
61+
runAsNonRoot: true
62+
runAsUser: 65532
63+
allowPrivilegeEscalation: false
64+
capabilities:
65+
drop: ["ALL"]
66+
resources:
67+
requests:
68+
cpu: "50m"
69+
memory: "64Mi"
70+
limits:
71+
cpu: "200m"
72+
memory: "128Mi"

k8s/base/service.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: v1
2+
kind: Service
3+
metadata:
4+
name: go-sample-app-svc
5+
spec:
6+
ports:
7+
- port: 80
8+
targetPort: 8080
9+
protocol: TCP
10+
name: http
11+
selector:
12+
app: go-sample-app

0 commit comments

Comments
 (0)