Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,64 @@
# TODO
name: Spring seminar CD

on:
push:
branches:
- main
workflow_dispatch:

jobs:
deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
picokosmos/springboot-seminar:${{ github.sha }}
picokosmos/springboot-seminar:latest
cache-from: type=registry,ref=picokosmos/springboot-seminar:latest
cache-to: type=inline

- name: Setup kubectl
uses: azure/setup-kubectl@v3

- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "${{ secrets.KUBECONFIG }}" > ~/.kube/config
chmod 600 ~/.kube/config

- name: Create/Update app secrets
run: |
kubectl create secret generic app-secrets \
--from-literal=db-password=${{ secrets.DB_PASSWORD }} \
--from-literal=redis-password=${{ secrets.REDIS_PASSWORD }} \
--from-literal=jwt-secret=${{ secrets.JWT_SECRET }} \
--dry-run=client -o yaml | kubectl apply -f -

- name: Deploy to k3s
run: |
kubectl set image deployment/springboot-app \
springboot-app=picokosmos/springboot-seminar:${{ github.sha }} \
--record
kubectl rollout status deployment/springboot-app --timeout=5m

- name: Verify deployment
run: |
kubectl get pods -l app=springboot-app
kubectl get svc springboot-service
32 changes: 31 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# TODO
name: Spring seminar CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3

- name: Build with Gradle
run: ./gradlew build -x test

- name: Run tests
run: ./gradlew test

- name: Build Docker image
run: docker build -t picokosmos/springboot-seminar:${{ github.sha }} .
32 changes: 31 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1,31 @@
# TODO
# Multi-stage build for Spring Boot application
FROM gradle:8.5-jdk17 AS build

WORKDIR /app

# Copy gradle files for dependency caching
COPY build.gradle.kts settings.gradle.kts ./
COPY gradle ./gradle

# Download dependencies
RUN gradle dependencies --no-daemon || true

# Copy source code
COPY src ./src

# Build the application
RUN gradle bootJar --no-daemon -x test

# Runtime stage
FROM openjdk:17-slim

WORKDIR /app

# Copy the built jar from build stage
COPY --from=build /app/build/libs/*.jar app.jar

# Expose port 8080
EXPOSE 8080

# Run the application
ENTRYPOINT ["java", "-jar", "app.jar"]
68 changes: 68 additions & 0 deletions k8s/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: springboot-app
labels:
app: springboot-app
spec:
replicas: 3
selector:
matchLabels:
app: springboot-app
template:
metadata:
labels:
app: springboot-app
spec:
containers:
- name: springboot-app
image: picokosmos/springboot-seminar:latest
ports:
- containerPort: 8080
env:
- name: DB_HOST
value: my-mysql.default.svc.cluster.local
- name: DB_PORT
value: "3306"
- name: DB_NAME
value: "appdb"
- name: DB_USER
value: "root"
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: db-password
- name: JWT_SECRET
valueFrom:
secretKeyRef:
name: app-secrets
key: jwt-secret
- name: JWT_EXPIRATION_IN_MS
value: "86400000"
- name: REDIS_HOST
value: my-redis-master.default.svc.cluster.local
- name: REDIS_PORT
value: "6379"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: redis-password
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
16 changes: 16 additions & 0 deletions k8s/ingress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: springboot-ingress
spec:
ingressClassName: traefik
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: springboot-service
port:
number: 8080
16 changes: 16 additions & 0 deletions k8s/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: springboot-service
labels:
app: springboot-app
spec:
type: NodePort
selector:
app: springboot-app
ports:
- port: 8080
targetPort: 8080
nodePort: 30080
protocol: TCP
name: http
15 changes: 10 additions & 5 deletions src/main/resources/application.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
spring:
datasource:
url: jdbc:mysql://localhost:3306/seminar2025?allowPublicKeyRetrieval=true
username: user
password: somepassword
url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?allowPublicKeyRetrieval=true
username: ${DB_USER}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: ${REDIS_HOST}
port: ${REDIS_PORT}
password: ${REDIS_PASSWORD}
flyway:
enabled: true

jwt:
secret: this-is-a-very-long-and-secure-secret-key-for-jwt-token-generation
expiration-in-ms: 86400000
secret: ${JWT_SECRET:this-is-a-very-long-and-secure-secret-key-for-jwt-token-generation}
expiration-in-ms: ${JWT_EXPIRATION_IN_MS:86400000}

logging:
level:
Expand Down
Loading