Add automatic Kubernetes deployment
This commit is contained in:
1 parent
ebb9789853
commit
3500561af7
10 files changed
+274
No files matched your search
@@ -0,0 +1,11 @@
|
||||
.git
|
||||
.gitea
|
||||
.gradle
|
||||
.idea
|
||||
android
|
||||
docs
|
||||
**/build
|
||||
**/data
|
||||
**/dist
|
||||
**/node_modules
|
||||
*.db
|
||||
@@ -0,0 +1,83 @@
|
||||
name: Build and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: shared-ci
|
||||
env:
|
||||
IMAGE_REGISTRY: git.uuard.com
|
||||
IMAGE_NAME: nicholas/box-manifest
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test server
|
||||
run: docker run --rm -v "$PWD/server:/src" -w /src golang:1.25-alpine go test ./...
|
||||
|
||||
- name: Log in to Gitea registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ env.IMAGE_REGISTRY }}
|
||||
username: nicholas
|
||||
password: ${{ secrets.CD_CD_BUILD_PUSH }}
|
||||
|
||||
- name: Build image
|
||||
run: docker build --tag "$IMAGE_REGISTRY/$IMAGE_NAME:$IMAGE_TAG" .
|
||||
|
||||
- name: Push image
|
||||
run: docker push "$IMAGE_REGISTRY/$IMAGE_NAME:$IMAGE_TAG"
|
||||
|
||||
- name: Prune images
|
||||
if: always()
|
||||
run: docker image prune --force
|
||||
|
||||
deploy:
|
||||
needs: build
|
||||
runs-on: box-manifest-deploy
|
||||
env:
|
||||
IMAGE_TAG: ${{ github.sha }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install kubectl
|
||||
env:
|
||||
KUBECTL_VERSION: v1.36.2
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl"
|
||||
curl -fsSLO "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl.sha256"
|
||||
printf '%s %s\n' "$(cat kubectl.sha256)" kubectl | sha256sum -c
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
install -m 0755 kubectl "$HOME/.local/bin/kubectl"
|
||||
printf '%s\n' "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Configure Kubernetes access
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p ~/.kube
|
||||
printf '%s' "$KUBE_CONFIG" | base64 -d > ~/.kube/config
|
||||
chmod 600 ~/.kube/config
|
||||
kubectl auth can-i update deployments --namespace nicholas
|
||||
|
||||
- name: Deploy Box Manifest
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sed -E -i "s#newTag: [0-9a-z]+#newTag: ${IMAGE_TAG}#" k8s/kustomization.yaml
|
||||
grep -q "newTag: ${IMAGE_TAG}$" k8s/kustomization.yaml
|
||||
kubectl apply --kustomize k8s
|
||||
if ! kubectl rollout status deployment/box-manifest \
|
||||
--namespace nicholas --timeout=180s; then
|
||||
kubectl rollout undo deployment/box-manifest --namespace nicholas
|
||||
kubectl rollout status deployment/box-manifest \
|
||||
--namespace nicholas --timeout=180s
|
||||
exit 1
|
||||
fi
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
FROM node:24-alpine AS web-build
|
||||
WORKDIR /src/web
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY web/ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM golang:1.25-alpine AS server-build
|
||||
WORKDIR /src/server
|
||||
COPY server/go.mod server/go.sum ./
|
||||
RUN go mod download
|
||||
COPY server/ ./
|
||||
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/box-manifest-server ./cmd/box-manifest-server
|
||||
|
||||
FROM alpine:3.22
|
||||
RUN addgroup -g 10001 box-manifest \
|
||||
&& adduser -D -H -u 10001 -G box-manifest box-manifest \
|
||||
&& apk add --no-cache ca-certificates
|
||||
WORKDIR /app
|
||||
COPY --from=server-build /out/box-manifest-server ./box-manifest-server
|
||||
COPY --from=web-build /src/web/dist ./web
|
||||
RUN mkdir /data && chown box-manifest:box-manifest /data
|
||||
USER 10001:10001
|
||||
EXPOSE 8080
|
||||
VOLUME ["/data"]
|
||||
ENTRYPOINT ["/app/box-manifest-server"]
|
||||
CMD ["-address", ":8080", "-database", "/data/box-manifest.db", "-web", "/app/web"]
|
||||
@@ -56,6 +56,27 @@ For a production-style local build, run `npm run build` before starting the Go
|
||||
server. The server serves the generated `web/dist` directory at
|
||||
`http://localhost:8080`.
|
||||
|
||||
## Container deployment
|
||||
|
||||
The root `Dockerfile` builds the web client and Go server into one image. Run it
|
||||
locally with a persistent data volume:
|
||||
|
||||
```bash
|
||||
docker build -t box-manifest .
|
||||
docker run --rm -p 8080:8080 -v box-manifest-data:/data box-manifest
|
||||
```
|
||||
|
||||
Kubernetes resources in `k8s/` deploy one server replica with a persistent
|
||||
SQLite volume at `/data`. The deployment intentionally uses the `Recreate`
|
||||
strategy so two server processes never write the database during an update.
|
||||
|
||||
Pushes to `master` build and publish an immutable image to the Gitea registry,
|
||||
then deploy it to the `nicholas` namespace. The repository requires these
|
||||
Gitea Actions secrets:
|
||||
|
||||
- `CD_CD_BUILD_PUSH`: registry token with package write access
|
||||
- `KUBE_CONFIG`: base64-encoded kubeconfig for the namespace deployer account
|
||||
|
||||
Available options:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: box-manifest
|
||||
labels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 5
|
||||
progressDeadlineSeconds: 180
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: gitea-registry
|
||||
securityContext:
|
||||
fsGroup: 10001
|
||||
fsGroupChangePolicy: OnRootMismatch
|
||||
containers:
|
||||
- name: box-manifest
|
||||
image: git.uuard.com/nicholas/box-manifest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 2
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: 10m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
runAsGroup: 10001
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: box-manifest-data
|
||||
@@ -0,0 +1,19 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: box-manifest
|
||||
labels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: box-manifest.uuard.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: box-manifest
|
||||
port:
|
||||
name: http
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: nicholas
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- pvc.yaml
|
||||
images:
|
||||
- name: git.uuard.com/nicholas/box-manifest
|
||||
newTag: initial
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: box-manifest-data
|
||||
labels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: box-manifest
|
||||
labels:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: box-manifest
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -42,6 +42,10 @@ func main() {
|
||||
|
||||
mux := http.NewServeMux()
|
||||
apiHandler := httpapi.New(store)
|
||||
mux.HandleFunc("GET /healthz", func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
_, _ = w.Write([]byte("ok\n"))
|
||||
})
|
||||
mux.Handle("/api/", apiHandler)
|
||||
mux.Handle("/n/", apiHandler)
|
||||
mux.Handle("/", newWebHandler(*webPath))
|
||||
|
||||
Reference in new issue
Block a user