From f6f4578995a49c3b1db5a247d7e72debc31af3ad Mon Sep 17 00:00:00 2001 From: Jisu Hong Date: Sun, 7 Dec 2025 14:50:39 +0900 Subject: [PATCH 1/3] role2 --- .github/workflows/deploy.yaml | 71 +++++++++++++++++++++++++++++++++++ Dockerfile | 32 +++++++++++++++- k8s/deployment.yaml | 64 +++++++++++++++++++++++++++++++ k8s/ingress.yaml | 18 +++++++++ k8s/service.yaml | 16 ++++++++ 5 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy.yaml create mode 100644 k8s/deployment.yaml create mode 100644 k8s/ingress.yaml create mode 100644 k8s/service.yaml diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..b00fedd --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,71 @@ +name: Build and Deploy to k3s + +on: + push: + branches: + - main + workflow_dispatch: + +env: + DOCKER_IMAGE: picokosmos/springboot-seminar + DOCKER_TAG: ${{ github.sha }} + +jobs: + build-and-push: + 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: | + ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} + ${{ env.DOCKER_IMAGE }}:latest + cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:latest + cache-to: type=inline + + deploy: + needs: build-and-push + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - 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: Verify kubectl connection + run: kubectl get nodes + + - name: Update deployment image + run: | + kubectl set image deployment/springboot-app \ + springboot-app=${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} \ + --record + + - name: Wait for rollout to complete + run: kubectl rollout status deployment/springboot-app --timeout=5m + + - name: Verify deployment + run: | + kubectl get pods -l app=springboot-app + kubectl get svc springboot-service diff --git a/Dockerfile b/Dockerfile index 4640904..8bf22f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] \ No newline at end of file diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml new file mode 100644 index 0000000..09bb855 --- /dev/null +++ b/k8s/deployment.yaml @@ -0,0 +1,64 @@ +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: SPRING_DATASOURCE_URL + value: "jdbc:mysql://mysql-service:3306/seminar2025?allowPublicKeyRetrieval=true" + - name: SPRING_DATASOURCE_USERNAME + valueFrom: + secretKeyRef: + name: mysql-secret + key: username + - name: SPRING_DATASOURCE_PASSWORD + valueFrom: + secretKeyRef: + name: mysql-secret + key: password + - name: SPRING_DATA_REDIS_HOST + value: "redis-service" + - name: SPRING_DATA_REDIS_PORT + value: "6379" + - name: JWT_SECRET + valueFrom: + secretKeyRef: + name: jwt-secret + key: secret + - name: JWT_EXPIRATION_IN_MS + value: "86400000" + livenessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /actuator/health + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 5 + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml new file mode 100644 index 0000000..1510cbf --- /dev/null +++ b/k8s/ingress.yaml @@ -0,0 +1,18 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: springboot-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + ingressClassName: traefik + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: springboot-service + port: + number: 8080 \ No newline at end of file diff --git a/k8s/service.yaml b/k8s/service.yaml new file mode 100644 index 0000000..78f45d4 --- /dev/null +++ b/k8s/service.yaml @@ -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 \ No newline at end of file From fc9b49bc1e5378a236bacb05ab3e91c028bfc142 Mon Sep 17 00:00:00 2001 From: Jisu Hong Date: Sun, 7 Dec 2025 14:56:22 +0900 Subject: [PATCH 2/3] =?UTF-8?q?deploy.yaml=20=EB=8C=80=EC=8B=A0=20ci.yml,?= =?UTF-8?q?=20cd.yml=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cd.yml | 53 +++++++++++++++++++++++++- .github/workflows/ci.yml | 32 +++++++++++++++- .github/workflows/deploy.yaml | 71 ----------------------------------- 3 files changed, 83 insertions(+), 73 deletions(-) delete mode 100644 .github/workflows/deploy.yaml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index c105b31..e225584 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,5 +1,56 @@ -# 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: 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 \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3247e6e..eee1e4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 }} . \ No newline at end of file diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml deleted file mode 100644 index b00fedd..0000000 --- a/.github/workflows/deploy.yaml +++ /dev/null @@ -1,71 +0,0 @@ -name: Build and Deploy to k3s - -on: - push: - branches: - - main - workflow_dispatch: - -env: - DOCKER_IMAGE: picokosmos/springboot-seminar - DOCKER_TAG: ${{ github.sha }} - -jobs: - build-and-push: - 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: | - ${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} - ${{ env.DOCKER_IMAGE }}:latest - cache-from: type=registry,ref=${{ env.DOCKER_IMAGE }}:latest - cache-to: type=inline - - deploy: - needs: build-and-push - runs-on: ubuntu-latest - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - 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: Verify kubectl connection - run: kubectl get nodes - - - name: Update deployment image - run: | - kubectl set image deployment/springboot-app \ - springboot-app=${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} \ - --record - - - name: Wait for rollout to complete - run: kubectl rollout status deployment/springboot-app --timeout=5m - - - name: Verify deployment - run: | - kubectl get pods -l app=springboot-app - kubectl get svc springboot-service From 3941f0ebe61f932032b5738939682680b705181f Mon Sep 17 00:00:00 2001 From: Jisu Hong Date: Sun, 7 Dec 2025 20:44:03 +0900 Subject: [PATCH 3/3] configured secrets and environment variables --- .github/workflows/cd.yml | 10 ++++++- k8s/deployment.yaml | 44 ++++++++++++++++------------- k8s/ingress.yaml | 2 -- src/main/resources/application.yaml | 15 ++++++---- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index e225584..fbd9ef8 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -43,6 +43,14 @@ jobs: 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 \ @@ -53,4 +61,4 @@ jobs: - name: Verify deployment run: | kubectl get pods -l app=springboot-app - kubectl get svc springboot-service \ No newline at end of file + kubectl get svc springboot-service diff --git a/k8s/deployment.yaml b/k8s/deployment.yaml index 09bb855..b7031db 100644 --- a/k8s/deployment.yaml +++ b/k8s/deployment.yaml @@ -20,38 +20,42 @@ spec: ports: - containerPort: 8080 env: - - name: SPRING_DATASOURCE_URL - value: "jdbc:mysql://mysql-service:3306/seminar2025?allowPublicKeyRetrieval=true" - - name: SPRING_DATASOURCE_USERNAME + - 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: mysql-secret - key: username - - name: SPRING_DATASOURCE_PASSWORD - valueFrom: - secretKeyRef: - name: mysql-secret - key: password - - name: SPRING_DATA_REDIS_HOST - value: "redis-service" - - name: SPRING_DATA_REDIS_PORT - value: "6379" + name: app-secrets + key: db-password - name: JWT_SECRET valueFrom: secretKeyRef: - name: jwt-secret - key: secret + 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: - httpGet: - path: /actuator/health + tcpSocket: port: 8080 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: - httpGet: - path: /actuator/health + tcpSocket: port: 8080 initialDelaySeconds: 30 periodSeconds: 5 diff --git a/k8s/ingress.yaml b/k8s/ingress.yaml index 1510cbf..4cf08f0 100644 --- a/k8s/ingress.yaml +++ b/k8s/ingress.yaml @@ -2,8 +2,6 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: springboot-ingress - annotations: - nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: traefik rules: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 88d3e6f..3df661e 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -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: