commit 7e119cad418b2567fd081e0e88edbb8e80005a28 Author: Scaffolder Date: Thu Mar 5 13:37:56 2026 +0000 initial commit Change-Id: I9c68c43e939d2c1a3b95a68b71ecc5ba861a4df5 diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml new file mode 100644 index 0000000..8b1f5e3 --- /dev/null +++ b/.gitea/workflows/build-push.yml @@ -0,0 +1,141 @@ +name: Build and Push to ACR + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + workflow_dispatch: {} + +concurrency: + group: - + cancel-in-progress: true + +env: + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + +jobs: + build: + name: Build and Push + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + cache: 'maven' + + - name: Install Maven + run: | + echo "Installing Maven..." + apt-get update -qq + apt-get install -y maven + mvn --version + echo "Maven installed" + + - name: Build with Maven + run: | + echo "Building online-boutique..." + mvn clean package -DskipTests -B + echo "Build completed" + + - name: Run tests + run: | + echo "Running tests..." + mvn test -B + echo "Tests passed" + + # ── Install Azure CLI ─────────────────────────────────────────────── + # act runners don't include az by default — install via Microsoft's + # official script which works on Debian/Ubuntu without sudo. + - name: Install Azure CLI + run: | + if command -v az &>/dev/null; then + echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)" + else + curl -sL https://aka.ms/InstallAzureCLIDeb | bash + fi + + # ── Authenticate to Azure ────────────────────────────────────────── + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + + echo "✓ Azure login successful" + + + - name: Get ACR details + id: acr + run: | + echo "Getting ACR details..." + ACR_NAME=$(az acr list --query "[0].name" -o tsv) + ACR_NAME="${ACR_NAME:-bstagecjotdevacr}" + ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io" + + # Validation + if [ -z "$ACR_NAME" ]; then + echo "❌ ACR_NAME is empty" + exit 1 + fi + + if [ -z "$ACR_LOGIN_SERVER" ]; then + echo "❌ ACR_LOGIN_SERVER is empty" + exit 1 + fi + + echo "ACR_NAME=$ACR_NAME" >> $GITHUB_ENV + echo "ACR_LOGIN_SERVER=$ACR_LOGIN_SERVER" >> $GITHUB_ENV + echo "✓ Using ACR: $ACR_LOGIN_SERVER" + + - name: ACR Login + run: | + echo "Logging into ACR..." + echo "ACR_NAME='$ACR_NAME'" + + ACR_TOKEN=$(az acr login --name "$ACR_NAME" --expose-token --output tsv --query accessToken) + docker login "$ACR_LOGIN_SERVER" --username "$AZURE_CLIENT_ID" --password "$ACR_TOKEN" + + echo "✓ ACR login successful" + + - name: Build and Push Docker Image + run: | + IMAGE_NAME="online-boutique" + IMAGE_TAG="" + IMAGE_FULL="/$IMAGE_NAME:$IMAGE_TAG" + IMAGE_LATEST="/$IMAGE_NAME:latest" + echo "Building Docker image..." + docker build -t $IMAGE_NAME:$IMAGE_TAG . + echo "Tagging images..." + docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_FULL + docker tag $IMAGE_NAME:$IMAGE_TAG $IMAGE_LATEST + echo "Pushing to ACR..." + docker push $IMAGE_FULL + docker push $IMAGE_LATEST + echo "Images pushed:" + echo " - $IMAGE_FULL" + echo " - $IMAGE_LATEST" + echo "IMAGE_FULL=$IMAGE_FULL" >> $GITHUB_ENV + + - name: Build Summary + run: | + echo "### Build Successful" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Application**: online-boutique" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: " >> $GITHUB_STEP_SUMMARY + + echo "- **Image**: " >> $GITHUB_STEP_SUMMARY + echo "- **ACR**: " >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Built, tested, and pushed to ACR!" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/deploy-humanitec.yml b/.gitea/workflows/deploy-humanitec.yml new file mode 100644 index 0000000..eadfddb --- /dev/null +++ b/.gitea/workflows/deploy-humanitec.yml @@ -0,0 +1,196 @@ +name: Deploy to Humanitec + +on: + # Runs automatically after Build and Push succeeds — prevents deploying before image exists + workflow_run: + workflows: ["Build and Push to ACR"] + types: + - completed + branches: [ "main" ] + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + default: 'development' + type: choice + options: + - development + - staging + - production + +env: + APP_ID: online-boutique + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + DEFAULT_ENV_ID: development + +jobs: + deploy: + name: Deploy to Humanitec + runs-on: ubuntu-latest + # Only deploy when build succeeded (or when manually dispatched) + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + permissions: + contents: read + id-token: write + outputs: + sha: NaN + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get short SHA + id: get-sha + run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + + - name: Install CLI tools + run: | + echo "Installing required tools..." + + # Install yq + echo "Installing yq..." + wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 + chmod +x /usr/local/bin/yq + yq --version + + # Install jq + echo "Installing jq..." + wget -qO /usr/local/bin/jq https://github.com/jqlang/jq/releases/latest/download/jq-linux-amd64 + chmod +x /usr/local/bin/jq + jq --version + + # Install humctl + echo "Installing humctl..." + cd /tmp + HUMCTL_VERSION=$(curl -s https://api.github.com/repos/humanitec/cli/releases/latest | jq -r '.tag_name') + wget -q https://github.com/humanitec/cli/releases/download/${HUMCTL_VERSION}/cli_${HUMCTL_VERSION#v}_linux_amd64.tar.gz + tar -xzf cli_${HUMCTL_VERSION#v}_linux_amd64.tar.gz + mv humctl /usr/local/bin/ + chmod +x /usr/local/bin/humctl + cd - + humctl version + + echo "Tools installed" + + # ── Install Azure CLI ─────────────────────────────────────────────── + # act runners don't include az by default — install via Microsoft's + # official script which works on Debian/Ubuntu without sudo. + - name: Install Azure CLI + run: | + if command -v az &>/dev/null; then + echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)" + else + curl -sL https://aka.ms/InstallAzureCLIDeb | bash + fi + + # ── Authenticate to Azure ────────────────────────────────────────── + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + + echo "✓ Azure login successful" + + - name: Get image reference + id: image + run: | + ACR_LOGIN_SERVER=$(az acr list --query "[0].loginServer" -o tsv) + COMMIT_SHA=NaN + IMAGE_FULL="${ACR_LOGIN_SERVER}/online-boutique:${COMMIT_SHA}" + + echo "IMAGE_FULL=$IMAGE_FULL" >> $GITHUB_ENV + echo "SHA: $COMMIT_SHA" + echo "✓ Image: $IMAGE_FULL" + + - name: Set environment + id: set-env + run: | + if [ "" = "workflow_dispatch" ]; then + ENV_ID="" + else + ENV_ID="$DEFAULT_ENV_ID" + fi + + # Set default if empty + ENV_ID="${ENV_ID:-development}" + + echo "ENV_ID=$ENV_ID" >> $GITHUB_OUTPUT + echo "ENV_ID=$ENV_ID" >> $GITHUB_ENV + echo "DEPLOYMENT_ID=$(date +%Y%m%d-%H%M%S)-NaN" >> $GITHUB_OUTPUT + echo "✓ Using environment: $ENV_ID" + + - name: Get Humanitec token from Key Vault + id: get-secret + run: | + echo "Retrieving Humanitec token from Key Vault..." + HUMANITEC_TOKEN=$(az keyvault secret show \ + --vault-name "bstage-cjot-dev-core-kv" \ + --name "humanitec-api-token" \ + --query "value" \ + --output tsv) + + if [ -z "$HUMANITEC_TOKEN" ]; then + echo "❌ Failed to retrieve Humanitec token" + exit 1 + fi + + echo "HUMANITEC_TOKEN=$HUMANITEC_TOKEN" >> $GITHUB_ENV + echo "✓ Humanitec token retrieved successfully" + + HUMANITEC_ORG=$(az keyvault secret show \ + --vault-name "bstage-cjot-dev-core-kv" \ + --name "humanitec-org-id" \ + --query "value" \ + --output tsv) + + if [ -z "$HUMANITEC_ORG" ]; then + echo "❌ Failed to retrieve Humanitec org" + exit 1 + fi + + echo "HUMANITEC_ORG=$HUMANITEC_ORG" >> $GITHUB_ENV + echo "✓ Humanitec org retrieved successfully" + + - name: Deploy via Humanitec Score + id: deploy + run: | + # Capture outputs from previous steps as environment variables + echo " Org: $HUMANITEC_ORG" + echo " App: $APP_ID" + echo " Env: $ENV_ID" + echo " Image: $IMAGE_FULL" + echo " sha: NaN" + + # Deploy using Score + humctl score deploy \ + --org "$HUMANITEC_ORG" \ + --app "$APP_ID" \ + --env "$ENV_ID" \ + --file score.yaml \ + --image containers.app=$IMAGE_FULL \ + --message "Deployment from commit NaN" \ + --token "$HUMANITEC_TOKEN" \ + -v 5 + + echo "✓ Deployment successful!" + + - name: Deployment Summary + run: | + DEPLOY_URL="https://app.humanitec.io/orgs/${HUMANITEC_ORG}/apps/${APP_ID}/envs/$ENV_ID" + GRAFANA_URL="https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=${APP_ID}" + + echo "### 🚀 Deployment Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "- **Application**: ${APP_ID}" >> $GITHUB_STEP_SUMMARY + echo "- **Environment**: $ENV_ID" >> $GITHUB_STEP_SUMMARY + echo "- **Image**: $IMAGE_FULL" >> $GITHUB_STEP_SUMMARY + echo "- **Commit**: NaN" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "📊 **Links:**" >> $GITHUB_STEP_SUMMARY + echo "- [Humanitec Console]($DEPLOY_URL)" >> $GITHUB_STEP_SUMMARY + echo "- [Grafana Dashboard]($GRAFANA_URL)" >> $GITHUB_STEP_SUMMARY + \ No newline at end of file diff --git a/.gitea/workflows/techdocs.yml b/.gitea/workflows/techdocs.yml new file mode 100644 index 0000000..50cb84d --- /dev/null +++ b/.gitea/workflows/techdocs.yml @@ -0,0 +1,107 @@ +name: Build and Publish TechDocs + +on: + workflow_run: + workflows: ["Build and Push to ACR"] + branches: [main] + types: [completed] + workflow_dispatch: {} + +env: + TECHDOCS_AZURE_BLOB_CONTAINER_NAME: + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_ACCOUNT_NAME: "bstagecjotdevsttechdocs" + ENTITY_NAMESPACE: default + ENTITY_KIND: component + ENTITY_NAME: online-boutique +jobs: + build-and-publish: + if: >- + ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 # full history for git-revision-date-localized plugin + + - name: read and set output + id: read_env + run: | + echo "$AZURE_FEDERATED_TOKEN_FILE" + env | grep AZURE + echo "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + + # act-based Gitea runners run as root — sudo is not available. + # apt-get is called directly; works whether root or not. + - name: Bootstrap pip + run: | + python3 --version + if python3 -m pip --version 2>/dev/null; then + echo "pip already available" + elif python3 -m ensurepip --version 2>/dev/null; then + python3 -m ensurepip --upgrade + else + apt-get update -qq + apt-get install -y python3-pip + fi + python3 -m pip install --upgrade pip + python3 -m pip --version + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install \ + mkdocs-techdocs-core==1.* \ + mkdocs-git-revision-date-localized-plugin \ + mkdocs-awesome-pages-plugin + + npm install -g @techdocs/cli + + # mkdocs has no dry-run flag — build into a temp dir to validate config + # and catch any broken links or missing pages early. + - name: Validate MkDocs config + run: mkdocs build --strict --site-dir /tmp/mkdocs-validate + + - name: Build TechDocs site + run: | + techdocs-cli generate \ + --source-dir . \ + --output-dir ./site \ + --no-docker \ + --verbose + + # act runners don't include az by default — install via Microsoft's + # official script which works on Debian/Ubuntu without sudo. + - name: Install Azure CLI + run: | + if command -v az &>/dev/null; then + echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)" + else + curl -sL https://aka.ms/InstallAzureCLIDeb | bash + fi + + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + + echo "✓ Azure login successful" + + - name: Publish TechDocs site + run: | + echo "$AZURE_ACCOUNT_NAME" + echo "$ENTITY_NAMESPACE" + echo "$ENTITY_KIND" + echo "$ENTITY_NAME" + techdocs-cli publish \ + --publisher-type azureBlobStorage \ + --storage-name "techdocs" \ + --azureAccountName "$AZURE_ACCOUNT_NAME" \ + --entity "$ENTITY_NAMESPACE/$ENTITY_KIND/$ENTITY_NAME" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2608b03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +# Java / Maven +target/ +!.mvn/wrapper/maven-wrapper.jar +*.class +*.jar +*.war +*.ear + +# IDE +.idea/ +*.iml +.vscode/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Build artifacts +build/ +dist/ +out/ + +# Spring Boot +spring-boot-starter-actuator/ + +# Test +.test/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cb1e639 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,40 @@ +# Multi-stage build for Java 17 Spring Boot application + +# Build stage +FROM maven:3.9-eclipse-temurin-17-alpine AS build +WORKDIR /build + +# Copy pom.xml and download dependencies (cached layer) +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copy source and build +COPY src ./src +RUN mvn clean package -DskipTests -B + +# Runtime stage +FROM eclipse-temurin:17-jre-alpine +WORKDIR /app + +# Create non-root user +RUN addgroup -g 1000 appuser && \ + adduser -D -u 1000 -G appuser appuser + +# Copy JAR from build stage +COPY --from=build /build/target/*.jar application.jar + +# Change ownership +RUN chown -R appuser:appuser /app + +# Switch to non-root user +USER appuser + +# Expose application port +EXPOSE 8080 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=60s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/actuator/health || exit 1 + +# Run application +ENTRYPOINT ["java", "-jar", "application.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..24bc431 --- /dev/null +++ b/README.md @@ -0,0 +1,252 @@ +# online-boutique + +> Java microservice via Golden Path + +## Overview + +This service was scaffolded using the **Java Golden Path** template in Backstage. It provides: + +- ✅ Production-ready Spring Boot 3.2 application +- ✅ Prometheus metrics integration +- ✅ Health checks and readiness probes +- ✅ Humanitec deployment orchestration +- ✅ GitOps-ready Kubernetes manifests +- ✅ CI/CD via GitHub Actions +- ✅ Grafana dashboard integration + +## Quick Start + +### Local Development + +```bash +# Build and run locally +./mvnw spring-boot:run + +# Or with Docker +docker build -t online-boutique:latest . +docker run -p 8080:8080 online-boutique:latest +``` + +Access the application: +- **Application**: http://localhost:8080 +- **Health**: http://localhost:8080/actuator/health +- **Metrics**: http://localhost:8080/actuator/prometheus + +### Build + +```bash +# Maven build +./mvnw clean package + +# Output: target/online-boutique-1.0.0-SNAPSHOT.jar +``` + +### Test + +```bash +./mvnw test +``` + +## Deployment + +### Humanitec (Primary) + +Deployments are automatically triggered on push to `main` branch via GitHub Actions: + +1. **Build**: Maven builds the JAR +2. **Container**: Docker image built and pushed to ACR +3. **Deploy**: Humanitec CLI deploys using `score.yaml` + +View deployment status: +- **Humanitec Console**: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique + +### ArgoCD (Fallback) + +If Humanitec is unavailable, deploy via ArgoCD using manifests in `deploy/`: + +```bash +kubectl apply -k deploy/ +``` + +## Monitoring + +### Grafana Dashboard + +View real-time metrics: +- **URL**: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique + +Metrics include: +- HTTP request rate and latency +- Error rates (4xx, 5xx) +- JVM memory and GC +- CPU usage +- Thread pools + +### Prometheus Metrics + +Exposed at `/actuator/prometheus`: +- `http_server_requests_seconds` - Request metrics +- `jvm_memory_used_bytes` - Memory usage +- `process_cpu_usage` - CPU utilization + +### Health Checks + +- **Liveness**: `/actuator/health/liveness` +- **Readiness**: `/actuator/health/readiness` +- **Overall**: `/actuator/health` + +## API Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Welcome message | +| `/api/status` | GET | Service status | +| `/actuator/health` | GET | Health check | +| `/actuator/prometheus` | GET | Prometheus metrics | +| `/actuator/info` | GET | Application info | + +## Configuration + +### Environment Variables + +- `SPRING_PROFILES_ACTIVE` - Active profile (dev, staging, production) +- `SERVER_PORT` - HTTP port (default: 8080) + +### Profiles + +- **development**: Local dev settings +- **production**: Production optimized settings + +## Architecture + +``` +┌─────────────────────────────────────────┐ +│ GitHub Actions (CI/CD) │ +│ - Build with Maven │ +│ - Push to ACR │ +│ - Deploy via Humanitec │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Humanitec Orchestrator │ +│ - Interprets score.yaml │ +│ - Provisions resources │ +│ - Deploys to AKS │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Azure AKS Cluster │ +│ - Running application pods │ +│ - Prometheus scraping metrics │ +│ - Ingress routing traffic │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Grafana Dashboard │ +│ - Visualizing metrics │ +│ - Alerting on issues │ +└─────────────────────────────────────────┘ +``` + +## Development + +### Adding New Endpoints + +Edit `src/main/java/com/kyndryl/goldenpath/GoldenPathApplication.java`: + +```java +@GetMapping("/api/hello") +public String hello() { + return "Hello, World!"; +} +``` + +### Adding Custom Metrics + +Inject `MeterRegistry` and create custom metrics: + +```java +@RestController +public class MyController { + private final Counter myCounter; + + public MyController(MeterRegistry registry) { + this.myCounter = Counter.builder("my_custom_counter") + .tag("service", "online-boutique") + .register(registry); + } + + @GetMapping("/api/data") + public String getData() { + myCounter.increment(); + return "data"; + } +} +``` + +### Database Integration + +Add dependency in `pom.xml`: + +```xml + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + runtime + +``` + +Update `score.yaml` to provision PostgreSQL via Humanitec: + +```yaml +resources: + database: + type: postgres + properties: + version: "15" +``` + +## Troubleshooting + +### Build Fails + +```bash +# Clean and rebuild +./mvnw clean install + +# Check dependencies +./mvnw dependency:tree +``` + +### Metrics Not Showing + +1. Verify endpoint: `curl localhost:8080/actuator/prometheus` +2. Check pod annotations in `deploy/deployment.yaml` +3. Verify Prometheus targets in Grafana + +### Deployment Fails + +1. Check GitHub Actions logs +2. Review Humanitec deployment logs +3. Check pod status: `kubectl get pods -n demo-apps` + +## Links + +- **Repository**: https://gitea.kyndemo.live/validate/online-boutique +- **Humanitec**: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique +- **Grafana**: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique +- **Backstage Catalog**: https://backstage.kyndemo.live/catalog/default/component/online-boutique + +## Support + +For issues or questions: +1. Check documentation in `docs/` +2. Review Backstage TechDocs +3. Contact Platform Engineering team diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000..4a1e99c --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,37 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: online-boutique + description: Java microservice via Golden Path + annotations: + humanitec.com/orgId: kyn-cjot + humanitec.com/appId: online-boutique + backstage.io/techdocs-ref: dir:. + backstage.io/kubernetes-namespace: "demo-apps" + backstage.io/kubernetes-label-selector: "app.kubernetes.io/name=online-boutique" + gitea.kyndemo.live/repo-slug: "validate/online-boutique" + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/actuator/prometheus" + grafana/grafana-instance: default + grafana/alert-label-selector: "app=online-boutique" + grafana/dashboard-selector: "uid == 'app-online-boutique'" + tags: + - java + - spring-boot + - humanitec + - golden-path + links: + - url: https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique + title: Humanitec Console + icon: dashboard + - url: https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique + title: Grafana Dashboard + icon: dashboard + - url: https://gitea.kyndemo.live/validate/online-boutique + title: Source Repository + icon: github +spec: + type: service + owner: "group:default/platform-engineering" + lifecycle: production diff --git a/deploy/deployment.yaml b/deploy/deployment.yaml new file mode 100644 index 0000000..22cb226 --- /dev/null +++ b/deploy/deployment.yaml @@ -0,0 +1,98 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: online-boutique + labels: + app: online-boutique + app.kubernetes.io/name: online-boutique + app.kubernetes.io/component: application + app.kubernetes.io/part-of: golden-path +spec: + replicas: 2 + selector: + matchLabels: + app: online-boutique + app.kubernetes.io/name: online-boutique + template: + metadata: + labels: + app: online-boutique + app.kubernetes.io/name: online-boutique + app.kubernetes.io/version: "1.0.0" + annotations: + # Prometheus scraping annotations + prometheus.io/scrape: "true" + prometheus.io/port: "8080" + prometheus.io/path: "/actuator/prometheus" + spec: + containers: + - name: app + image: bstagecjotdevacr.azurecr.io/online-boutique:latest + imagePullPolicy: Always + ports: + - name: http + containerPort: 8080 + protocol: TCP + + # Environment variables + env: + - name: SPRING_PROFILES_ACTIVE + value: "development" + - name: ENVIRONMENT + value: "development" + + # Startup probe - gives app time to start + startupProbe: + httpGet: + path: /actuator/health/liveness + port: http + initialDelaySeconds: 10 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 30 # 150 seconds total + + # Liveness probe - restarts container if app is dead + livenessProbe: + httpGet: + path: /actuator/health/liveness + port: http + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + + # Readiness probe - removes from load balancer if not ready + readinessProbe: + httpGet: + path: /actuator/health/readiness + port: http + initialDelaySeconds: 0 + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 3 + + # Resource limits and requests + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + + # Security context + securityContext: + runAsNonRoot: true + runAsUser: 1000 + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + capabilities: + drop: + - ALL + + # Pod-level security context + securityContext: + fsGroup: 1000 + + # Graceful shutdown + terminationGracePeriodSeconds: 30 diff --git a/deploy/kustomization.yaml b/deploy/kustomization.yaml new file mode 100644 index 0000000..5e534b6 --- /dev/null +++ b/deploy/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: demo-apps + +resources: + - deployment.yaml + - service.yaml + - servicemonitor.yaml + +commonLabels: + app.kubernetes.io/managed-by: backstage + backstage.io/component-id: online-boutique + +# Image transformation (can be overridden by Kustomize overlays) +images: + - name: bstagecjotdevacr.azurecr.io/online-boutique + newTag: latest diff --git a/deploy/service.yaml b/deploy/service.yaml new file mode 100644 index 0000000..2416d7e --- /dev/null +++ b/deploy/service.yaml @@ -0,0 +1,18 @@ +apiVersion: v1 +kind: Service +metadata: + name: online-boutique + labels: + app: online-boutique + app.kubernetes.io/name: online-boutique + app.kubernetes.io/component: application +spec: + type: ClusterIP + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + name: http + selector: + app: online-boutique + app.kubernetes.io/name: online-boutique diff --git a/deploy/servicemonitor.yaml b/deploy/servicemonitor.yaml new file mode 100644 index 0000000..693f2b3 --- /dev/null +++ b/deploy/servicemonitor.yaml @@ -0,0 +1,16 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: online-boutique + labels: + app: online-boutique + prometheus: kube-prometheus +spec: + selector: + matchLabels: + app: online-boutique + endpoints: + - port: http + path: /actuator/prometheus + interval: 30s + scrapeTimeout: 10s diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..02439cb --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,521 @@ +# Architecture + +This document describes the architecture of online-boutique. + +## System Overview + +``` +┌──────────────────────────────────────────────────────────────┐ +│ Developer │ +│ │ +│ Backstage UI → Template → Gitea Repo → CI/CD Workflows │ +└────────────────────┬─────────────────────────────────────────┘ + │ + │ git push + ▼ +┌──────────────────────────────────────────────────────────────┐ +│ Gitea Actions │ +│ │ +│ ┌───────────────┐ ┌──────────────────┐ │ +│ │ Build & Push │──────▶│ Deploy Humanitec │ │ +│ │ - Maven │ │ - humctl score │ │ +│ │ - Docker │ │ - Environment │ │ +│ │ - ACR Push │ │ - Orchestration │ │ +│ └───────────────┘ └──────────────────┘ │ +└─────────────┬─────────────────┬──────────────────────────────┘ + │ │ + │ image │ deployment + ▼ ▼ +┌────────────────────┐ ┌────────────────────────────────────┐ +│ Azure Container │ │ Humanitec Platform │ +│ Registry │ │ │ +│ │ │ ┌──────────────────────────────┐ │ +│ bstagecjotdevacr │ │ │ Score Interpretation │ │ +│ │ │ │ Resource Provisioning │ │ +│ Images: │ │ │ Environment Management │ │ +│ - app:latest │ │ └──────────────────────────────┘ │ +│ - app:v1.0.0 │ │ │ │ +│ - app:git-sha │ │ │ kubectl apply │ +└────────────────────┘ └─────────────┼──────────────────────┘ + │ + ▼ + ┌─────────────────────────────────────────────┐ + │ Azure Kubernetes Service (AKS) │ + │ │ + │ ┌────────────────────────────────────┐ │ + │ │ Namespace: │ │ + │ │ │ │ + │ │ ┌──────────────────────────────┐ │ │ + │ │ │ Deployment │ │ │ + │ │ │ - Replicas: 2 │ │ │ + │ │ │ - Health Probes │ │ │ + │ │ │ - Resource Limits │ │ │ + │ │ │ │ │ │ + │ │ │ ┌───────────┐ ┌──────────┐ │ │ │ + │ │ │ │ Pod │ │ Pod │ │ │ │ + │ │ │ │ Spring │ │ Spring │ │ │ │ + │ │ │ │ Boot │ │ Boot │ │ │ │ + │ │ │ │ :8080 │ │ :8080 │ │ │ │ + │ │ │ └─────┬─────┘ └────┬─────┘ │ │ │ + │ │ └────────┼────────────┼───────┘ │ │ + │ │ │ │ │ │ + │ │ ┌────────▼────────────▼───────┐ │ │ + │ │ │ Service (ClusterIP) │ │ │ + │ │ │ - Port: 80 → 8080 │ │ │ + │ │ └────────┬───────────────────┘ │ │ + │ │ │ │ │ + │ │ ┌────────▼───────────────────┐ │ │ + │ │ │ Ingress │ │ │ + │ │ │ - TLS (cert-manager) │ │ │ + │ │ │ - Host: app.kyndemo.live │ │ │ + │ │ └────────────────────────────┘ │ │ + │ └────────────────────────────────┘ │ + │ │ + │ ┌─────────────────────────────────┐ │ + │ │ Monitoring Namespace │ │ + │ │ │ │ + │ │ ┌────────────────────────────┐ │ │ + │ │ │ Prometheus │ │ │ + │ │ │ - ServiceMonitor │ │ │ + │ │ │ - Scrapes /actuator/ │ │ │ + │ │ │ prometheus every 30s │ │ │ + │ │ └────────────────────────────┘ │ │ + │ │ │ │ + │ │ ┌────────────────────────────┐ │ │ + │ │ │ Grafana │ │ │ + │ │ │ - Spring Boot Dashboard │ │ │ + │ │ │ - Alerts │ │ │ + │ │ └────────────────────────────┘ │ │ + │ └─────────────────────────────────┘ │ + └─────────────────────────────────────────┘ +``` + +## Component Architecture + +### 1. Application Layer + +#### Spring Boot Application + +**Technology Stack:** +- **Framework**: Spring Boot 3.2 +- **Java**: OpenJDK 17 (LTS) +- **Build**: Maven 3.9 +- **Runtime**: Embedded Tomcat + +**Key Components:** + +```java +@SpringBootApplication +public class GoldenPathApplication { + // Auto-configuration + // Component scanning + // Property binding +} + +@RestController +public class ApiController { + @GetMapping("/") + public String root(); + + @GetMapping("/api/status") + public ResponseEntity> status(); +} +``` + +**Configuration Management:** +- `application.yml`: Base configuration +- `application-development.yml`: Dev overrides +- `application-production.yml`: Production overrides +- Environment variables: Runtime overrides + +### 2. Container Layer + +#### Docker Image + +**Multi-stage Build:** + +```dockerfile +# Stage 1: Build +FROM maven:3.9-eclipse-temurin-17 AS builder +WORKDIR /app +COPY pom.xml . +RUN mvn dependency:go-offline +COPY src ./src +RUN mvn package -DskipTests + +# Stage 2: Runtime +FROM eclipse-temurin:17-jre-alpine +WORKDIR /app +COPY --from=builder /app/target/*.jar app.jar +USER 1000 +EXPOSE 8080 +ENTRYPOINT ["java", "-jar", "app.jar"] +``` + +**Optimizations:** +- Layer caching for dependencies +- Minimal runtime image (Alpine) +- Non-root user (UID 1000) +- Health check support + +### 3. Orchestration Layer + +#### Humanitec Score + +**Resource Specification:** + +```yaml +apiVersion: score.dev/v1b1 +metadata: + name: online-boutique + +containers: + app: + image: bstagecjotdevacr.azurecr.io/online-boutique:latest + resources: + requests: + memory: 512Mi + cpu: 250m + limits: + memory: 1Gi + cpu: 1000m + +service: + ports: + http: + port: 80 + targetPort: 8080 + +resources: + route: + type: route + params: + host: online-boutique.kyndemo.live +``` + +**Capabilities:** +- Environment-agnostic deployment +- Resource dependencies +- Configuration management +- Automatic rollback + +#### Kubernetes Resources + +**Fallback Manifests:** +- `deployment.yaml`: Pod specification, replicas, health probes +- `service.yaml`: ClusterIP service for internal routing +- `ingress.yaml`: External access with TLS +- `servicemonitor.yaml`: Prometheus scraping config + +### 4. CI/CD Pipeline + +#### Build & Push Workflow + +**Stages:** + +1. **Checkout**: Clone repository +2. **Setup**: Install Maven, Docker +3. **Test**: Run unit & integration tests +4. **Build**: Maven package +5. **Docker**: Build multi-stage image +6. **Auth**: Azure OIDC login +7. **Push**: Push to ACR with tags + +**Triggers:** +- Push to `main` branch +- Pull requests +- Manual dispatch + +#### Deploy Workflow + +**Stages:** + +1. **Parse Image**: Extract image reference from build +2. **Setup**: Install humctl CLI +3. **Score Update**: Replace image in score.yaml +4. **Deploy**: Execute humctl score deploy +5. **Verify**: Check deployment status + +**Secrets:** +- `HUMANITEC_TOKEN`: Platform authentication +- `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`: OIDC federation + +### 5. Observability Layer + +#### Metrics Collection + +**Flow:** + +``` +Spring Boot App + │ + └── /actuator/prometheus (HTTP endpoint) + │ + └── Prometheus (scrape every 30s) + │ + └── TSDB (15-day retention) + │ + └── Grafana (visualization) +``` + +**ServiceMonitor Configuration:** + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +spec: + selector: + matchLabels: + app: online-boutique + endpoints: + - port: http + path: /actuator/prometheus + interval: 30s +``` + +#### Metrics Categories + +1. **HTTP Metrics**: + - Request count/rate + - Response time (avg, p95, p99) + - Status code distribution + +2. **JVM Metrics**: + - Heap/non-heap memory + - GC pause time + - Thread count + +3. **System Metrics**: + - CPU usage + - File descriptors + - Process uptime + +## Data Flow + +### Request Flow + +``` +User Request + │ + ▼ +Ingress Controller (nginx) + │ TLS termination + │ Host routing + ▼ +Service (ClusterIP) + │ Load balancing + │ Port mapping + ▼ +Pod (Spring Boot) + │ Request handling + │ Business logic + ▼ +Response +``` + +### Metrics Flow + +``` +Spring Boot (Micrometer) + │ Collect metrics + │ Format Prometheus + ▼ +Actuator Endpoint + │ Expose /actuator/prometheus + ▼ +Prometheus (Scraper) + │ Pull every 30s + │ Store in TSDB + ▼ +Grafana + │ Query PromQL + │ Render dashboards + ▼ +User Visualization +``` + +### Deployment Flow + +``` +Git Push + │ + ▼ +Gitea Actions (Webhook) + │ + ├── Build Workflow + │ │ Maven test + package + │ │ Docker build + │ │ ACR push + │ └── Output: image reference + │ + └── Deploy Workflow + │ Parse image + │ Update score.yaml + │ humctl score deploy + │ + ▼ +Humanitec Platform + │ Interpret Score + │ Provision resources + │ Generate manifests + │ + ▼ +Kubernetes API + │ Apply deployment + │ Create/update resources + │ Schedule pods + │ + ▼ +Running Application +``` + +## Security Architecture + +### Authentication & Authorization + +1. **Azure Workload Identity**: + - OIDC federation for CI/CD + - No static credentials + - Scoped permissions + +2. **Service Account**: + - Kubernetes ServiceAccount + - Bound to Azure Managed Identity + - Limited RBAC + +3. **Image Pull Secrets**: + - AKS ACR integration + - Managed identity for registry access + +### Network Security + +1. **Ingress**: + - TLS 1.2+ only + - Cert-manager for automatic cert renewal + - Rate limiting (optional) + +2. **Network Policies**: + - Restrict pod-to-pod communication + - Allow only required egress + +3. **Service Mesh (Future)**: + - mTLS between services + - Fine-grained authorization + +### Application Security + +1. **Container**: + - Non-root user (UID 1000) + - Read-only root filesystem + - No privilege escalation + +2. **Dependencies**: + - Regular Maven dependency updates + - Vulnerability scanning (Snyk/Trivy) + +3. **Secrets Management**: + - Azure Key Vault integration + - CSI driver for secret mounting + - No secrets in environment variables + +## Scalability + +### Horizontal Scaling + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +spec: + minReplicas: 2 + maxReplicas: 10 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 +``` + +### Vertical Scaling + +Use **VPA (Vertical Pod Autoscaler)** for automatic resource recommendation. + +### Database Scaling (Future) + +- Connection pooling (HikariCP) +- Read replicas for read-heavy workloads +- Caching layer (Redis) + +## High Availability + +### Application Level +- **Replicas**: Minimum 2 pods per environment +- **Anti-affinity**: Spread across nodes +- **Readiness probes**: Only route to healthy pods + +### Infrastructure Level +- **AKS**: Multi-zone node pools +- **Ingress**: Multiple replicas with PodDisruptionBudget +- **Monitoring**: High availability via Thanos + +## Disaster Recovery + +### Backup Strategy +1. **Application State**: Stateless, no backup needed +2. **Configuration**: Stored in Git +3. **Metrics**: 15-day retention, export to long-term storage +4. **Container Images**: Retained in ACR with retention policy + +### Recovery Procedures +1. **Pod failure**: Automatic restart by kubelet +2. **Node failure**: Automatic rescheduling to healthy nodes +3. **Cluster failure**: Redeploy via Terraform + Humanitec +4. **Regional failure**: Failover to secondary region (if configured) + +## Technology Decisions + +### Why Spring Boot? +- Industry-standard Java framework +- Rich ecosystem (Actuator, Security, Data) +- Production-ready features out of the box +- Easy testing and debugging + +### Why Humanitec? +- Environment-agnostic deployment +- Score specification simplicity +- Resource dependency management +- Reduces K8s complexity + +### Why Prometheus + Grafana? +- Cloud-native standard +- Rich query language (PromQL) +- Wide integration support +- Open-source, vendor-neutral + +### Why Maven? +- Mature dependency management +- Extensive plugin ecosystem +- Declarative configuration +- Wide adoption in Java community + +## Future Enhancements + +1. **Database Integration**: PostgreSQL with Flyway migrations +2. **Caching**: Redis for session storage +3. **Messaging**: Kafka for event-driven architecture +4. **Tracing**: Jaeger/Zipkin for distributed tracing +5. **Service Mesh**: Istio for advanced traffic management +6. **Multi-region**: Active-active deployment + +## Next Steps + +- [Review deployment guide](deployment.md) +- [Configure monitoring](monitoring.md) +- [Return to overview](index.md) diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..a31e27b --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,384 @@ +# Deployment Guide + +This guide covers deploying online-boutique to Azure Kubernetes Service via Humanitec or ArgoCD. + +## Deployment Methods + +### 1. Humanitec Platform Orchestrator (Primary) + +Humanitec manages deployments using the `score.yaml` specification, automatically provisioning resources and handling promotions across environments. + +#### Prerequisites + +- Humanitec Organization: `kyn-cjot` +- Application registered in Humanitec +- Environments created (development, staging, production) +- Gitea Actions configured with HUMANITEC_TOKEN secret + +#### Automatic Deployment (via Gitea Actions) + +Push to trigger workflows: + +```bash +git add . +git commit -m "feat: new feature" +git push origin main +``` + +**Build & Push Workflow** (`.gitea/workflows/build-push.yml`): +1. Maven build & test +2. Docker image build +3. Push to Azure Container Registry (ACR) +4. Tags: `latest`, `git-SHA`, `semantic-version` + +**Deploy Workflow** (`.gitea/workflows/deploy-humanitec.yml`): +1. Parses image from build +2. Updates score.yaml with image reference +3. Deploys to Humanitec environment +4. Triggers orchestration + +#### Manual Deployment with humctl CLI + +Install Humanitec CLI: + +```bash +# macOS +brew install humanitec/tap/humctl + +# Linux/Windows +curl -s https://get.humanitec.io/install.sh | bash +``` + +Login: + +```bash +humctl login --org kyn-cjot +``` + +Deploy from Score: + +```bash +humctl score deploy \ + --org kyn-cjot \ + --app online-boutique \ + --env development \ + --file score.yaml \ + --image bstagecjotdevacr.azurecr.io/online-boutique:latest \ + --message "Manual deployment from local" +``` + +Deploy specific version: + +```bash +humctl score deploy \ + --org kyn-cjot \ + --app online-boutique \ + --env production \ + --file score.yaml \ + --image bstagecjotdevacr.azurecr.io/online-boutique:v1.2.3 \ + --message "Production release v1.2.3" +``` + +#### Environment Promotion + +Promote from development → staging: + +```bash +humctl deploy \ + --org kyn-cjot \ + --app online-boutique \ + --env staging \ + --from development \ + --message "Promote to staging after testing" +``` + +Promote to production: + +```bash +humctl deploy \ + --org kyn-cjot \ + --app online-boutique \ + --env production \ + --from staging \ + --message "Production release" +``` + +#### Check Deployment Status + +```bash +# List deployments +humctl get deployments \ + --org kyn-cjot \ + --app online-boutique \ + --env development + +# Get specific deployment +humctl get deployment \ + --org kyn-cjot \ + --app online-boutique \ + --env development + +# View deployment logs +humctl logs \ + --org kyn-cjot \ + --app online-boutique \ + --env development +``` + +### 2. ArgoCD GitOps (Fallback) + +If Humanitec is unavailable, use ArgoCD with Kubernetes manifests in `deploy/`. + +#### Create ArgoCD Application + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: online-boutique + namespace: argocd +spec: + project: default + source: + repoURL: https://gitea.kyndemo.live/validate/online-boutique.git + targetRevision: main + path: deploy + destination: + server: https://kubernetes.default.svc + namespace: + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - CreateNamespace=true +``` + +Apply: + +```bash +kubectl apply -f argocd-app.yaml +``` + +#### Manual Deploy with kubectl + +Update image in `deploy/kustomization.yaml`: + +```yaml +images: + - name: app-image + newName: bstagecjotdevacr.azurecr.io/online-boutique + newTag: v1.2.3 +``` + +Deploy: + +```bash +kubectl apply -k deploy/ +``` + +Verify: + +```bash +kubectl -n get pods +kubectl -n get svc +kubectl -n get ing +``` + +## Kubernetes Access + +### Get AKS Credentials + +```bash +az aks get-credentials \ + --resource-group bstage-cjot-dev \ + --name bstage-cjot-dev-aks \ + --overwrite-existing +``` + +### View Application + +```bash +# List pods +kubectl -n get pods + +# Check pod logs +kubectl -n logs -f deployment/online-boutique + +# Describe deployment +kubectl -n describe deployment online-boutique + +# Port-forward for local access +kubectl -n port-forward svc/online-boutique-service 8080:80 +``` + +### Check Health + +```bash +# Health endpoint +kubectl -n exec -it deployment/online-boutique -- \ + curl http://localhost:8080/actuator/health + +# Metrics endpoint +kubectl -n exec -it deployment/online-boutique -- \ + curl http://localhost:8080/actuator/prometheus +``` + +## Environment Configuration + +### Development + +- **Purpose**: Active development, frequent deployments +- **Image Tag**: `latest` or `git-SHA` +- **Replicas**: 1 +- **Resources**: Minimal (requests: 256Mi RAM, 250m CPU) +- **Monitoring**: Prometheus scraping enabled + +### Staging + +- **Purpose**: Pre-production testing, integration tests +- **Image Tag**: Semantic version (e.g., `v1.2.3-rc.1`) +- **Replicas**: 2 +- **Resources**: Production-like (requests: 512Mi RAM, 500m CPU) +- **Monitoring**: Full observability stack + +### Production + +- **Purpose**: Live traffic, stable releases +- **Image Tag**: Semantic version (e.g., `v1.2.3`) +- **Replicas**: 3+ (autoscaling) +- **Resources**: Right-sized (requests: 1Gi RAM, 1 CPU) +- **Monitoring**: Alerts enabled, SLO tracking + +## Rollback Procedures + +### Humanitec Rollback + +```bash +# List previous deployments +humctl get deployments \ + --org kyn-cjot \ + --app online-boutique \ + --env production + +# Rollback to specific deployment +humctl deploy \ + --org kyn-cjot \ + --app online-boutique \ + --env production \ + --deployment-id \ + --message "Rollback due to issue" +``` + +### Kubernetes Rollback + +```bash +# Rollback to previous revision +kubectl -n rollout undo deployment/online-boutique + +# Rollback to specific revision +kubectl -n rollout undo deployment/online-boutique --to-revision=2 + +# Check rollout status +kubectl -n rollout status deployment/online-boutique + +# View rollout history +kubectl -n rollout history deployment/online-boutique +``` + +## Troubleshooting + +### Pod Not Starting + +```bash +# Check pod events +kubectl -n describe pod + +# Check logs +kubectl -n logs + +# Check previous container logs (if restarting) +kubectl -n logs --previous +``` + +### Image Pull Errors + +```bash +# Verify ACR access +az acr login --name bstagecjotdevacr + +# Check image exists +az acr repository show-tags --name bstagecjotdevacr --repository online-boutique + +# Verify AKS ACR integration +az aks check-acr \ + --resource-group bstage-cjot-dev \ + --name bstage-cjot-dev-aks \ + --acr bstagecjotdevacr.azurecr.io +``` + +### Service Not Accessible + +```bash +# Check service endpoints +kubectl -n get endpoints online-boutique-service + +# Check ingress +kubectl -n describe ingress online-boutique-ingress + +# Test internal connectivity +kubectl -n run curl-test --image=curlimages/curl:latest --rm -it --restart=Never -- \ + curl http://online-boutique-service/actuator/health +``` + +### Humanitec Deployment Stuck + +```bash +# Check deployment status +humctl get deployment \ + --org kyn-cjot \ + --app online-boutique \ + --env development + +# View error logs +humctl logs \ + --org kyn-cjot \ + --app online-boutique \ + --env development \ + --deployment-id + +# Cancel stuck deployment +humctl delete deployment \ + --org kyn-cjot \ + --app online-boutique \ + --env development +``` + +### Resource Issues + +```bash +# Check resource usage +kubectl -n top pods + +# Describe pod for resource constraints +kubectl -n describe pod | grep -A 10 "Conditions:" + +# Check node capacity +kubectl describe nodes | grep -A 10 "Allocated resources:" +``` + +## Blue-Green Deployments + +For zero-downtime deployments with Humanitec: + +1. Deploy new version to staging +2. Run smoke tests +3. Promote to production with traffic splitting +4. Monitor metrics +5. Complete cutover or rollback + +## Next Steps + +- [Configure monitoring](monitoring.md) +- [Review architecture](architecture.md) +- [Return to overview](index.md) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e006d19 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,130 @@ +# online-boutique + +## Overview + +**online-boutique** is a production-ready Java microservice built using the Kyndryl Platform Engineering Golden Path. + +!!! info "Service Information" + - **Description**: Java microservice via Golden Path + - **Environment**: development + - **Technology**: Spring Boot 3.2, Java 17 + - **Orchestration**: Humanitec + - **Observability**: Prometheus + Grafana + +## Quick Links + +- [Repository](https://gitea.kyndemo.live/validate/online-boutique) +- [Humanitec Console](https://app.humanitec.io/orgs/kyn-cjot/apps/online-boutique) +- [Grafana Dashboard](https://grafana.kyndemo.live/d/spring-boot-dashboard?var-app=online-boutique) + +## Features + +✅ **Production-Ready Configuration** +- Health checks (liveness, readiness, startup) +- Graceful shutdown +- Resource limits and requests +- Security contexts + +✅ **Observability** +- Prometheus metrics integration +- Pre-configured Grafana dashboards +- Structured logging +- Request tracing + +✅ **CI/CD** +- Automated builds via GitHub Actions +- Azure Container Registry integration +- Humanitec deployment automation +- GitOps fallback with ArgoCD + +✅ **Developer Experience** +- Local development support +- Hot reload with Spring DevTools +- Comprehensive tests +- API documentation + +## Architecture + +This service follows the golden path architecture: + +``` +┌─────────────────────────────────────────┐ +│ Developer Experience │ +│ (Backstage Template → Gitea Repo) │ +└─────────────────────────────────────────┘ + │ + │ git push + ▼ +┌─────────────────────────────────────────┐ +│ GitHub Actions CI/CD │ +│ 1. Build with Maven │ +│ 2. Run tests │ +│ 3. Build Docker image │ +│ 4. Push to ACR │ +│ 5. Deploy via Humanitec │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Humanitec Orchestrator │ +│ - Interprets score.yaml │ +│ - Provisions resources │ +│ - Deploys to AKS │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Azure AKS Cluster │ +│ - Pods with app containers │ +│ - Prometheus scraping metrics │ +│ - Service mesh (optional) │ +└─────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────┐ +│ Grafana + Prometheus │ +│ - Real-time metrics │ +│ - Dashboards │ +│ - Alerting │ +└─────────────────────────────────────────┘ +``` + +## API Endpoints + +### Application Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/` | GET | Welcome message | +| `/api/status` | GET | Service health status | + +### Actuator Endpoints + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/actuator/health` | GET | Overall health | +| `/actuator/health/liveness` | GET | Liveness probe | +| `/actuator/health/readiness` | GET | Readiness probe | +| `/actuator/metrics` | GET | Available metrics | +| `/actuator/prometheus` | GET | Prometheus metrics | +| `/actuator/info` | GET | Application info | + +## Technology Stack + +- **Language**: Java 17 +- **Framework**: Spring Boot 3.2.0 +- **Build Tool**: Maven 3.9 +- **Metrics**: Micrometer + Prometheus +- **Container**: Docker (Alpine-based) +- **Orchestration**: Humanitec (Score) +- **CI/CD**: GitHub Actions +- **Registry**: Azure Container Registry +- **Kubernetes**: Azure AKS +- **Monitoring**: Prometheus + Grafana + +## Next Steps + +- [Set up local development environment](local-development.md) +- [Learn about deployment process](deployment.md) +- [Configure monitoring and alerts](monitoring.md) +- [Understand the architecture](architecture.md) diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 0000000..a96c407 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,279 @@ +# Local Development + +This guide covers setting up and running online-boutique on your local machine. + +## Prerequisites + +- **Java 17** or higher ([Download](https://adoptium.net/)) +- **Maven 3.9+** (included via Maven Wrapper) +- **Docker** (optional, for container testing) +- **Git** + +## Quick Start + +### 1. Clone the Repository + +```bash +git clone https://gitea.kyndemo.live/validate/online-boutique.git +cd online-boutique +``` + +### 2. Build the Application + +```bash +# Using Maven Wrapper (recommended) +./mvnw clean package + +# Or with system Maven +mvn clean package +``` + +### 3. Run the Application + +```bash +# Run with Spring Boot Maven plugin +./mvnw spring-boot:run + +# Or run the JAR directly +java -jar target/online-boutique-1.0.0-SNAPSHOT.jar +``` + +The application will start on **http://localhost:8080** + +### 4. Verify It's Running + +```bash +# Check health +curl http://localhost:8080/actuator/health + +# Check status +curl http://localhost:8080/api/status + +# View metrics +curl http://localhost:8080/actuator/prometheus +``` + +## Development Workflow + +### Hot Reload with Spring DevTools + +For automatic restarts during development, add Spring DevTools to `pom.xml`: + +```xml + + org.springframework.boot + spring-boot-devtools + runtime + true + +``` + +Changes to Java files will trigger automatic restarts. + +### Running Tests + +```bash +# Run all tests +./mvnw test + +# Run specific test class +./mvnw test -Dtest=GoldenPathApplicationTests + +# Run tests with coverage +./mvnw test jacoco:report +``` + +### Active Profile + +Set active profile via environment variable: + +```bash +# Development profile +export SPRING_PROFILES_ACTIVE=development +./mvnw spring-boot:run + +# Or inline +SPRING_PROFILES_ACTIVE=development ./mvnw spring-boot:run +``` + +## Docker Development + +### Build Image Locally + +```bash +docker build -t online-boutique:dev . +``` + +### Run in Docker + +```bash +docker run -p 8080:8080 \ + -e SPRING_PROFILES_ACTIVE=development \ + online-boutique:dev +``` + +### Docker Compose (if needed) + +Create `docker-compose.yml`: + +```yaml +version: '3.8' +services: + app: + build: . + ports: + - "8080:8080" + environment: + - SPRING_PROFILES_ACTIVE=development +``` + +Run with: +```bash +docker-compose up +``` + +## IDE Setup + +### IntelliJ IDEA + +1. **Import Project**: File → New → Project from Existing Sources +2. **Select Maven**: Choose Maven as build tool +3. **SDK**: Configure Java 17 SDK +4. **Run Configuration**: + - Main class: `com.kyndryl.goldenpath.GoldenPathApplication` + - VM options: `-Dspring.profiles.active=development` + +### VS Code + +1. **Install Extensions**: + - Extension Pack for Java + - Spring Boot Extension Pack + +2. **Open Folder**: Open the project root + +3. **Run/Debug**: Use Spring Boot Dashboard or F5 + +### Eclipse + +1. **Import**: File → Import → Maven → Existing Maven Projects +2. **Update Project**: Right-click → Maven → Update Project +3. **Run**: Right-click on Application class → Run As → Java Application + +## Debugging + +### Enable Debug Logging + +In `application-development.yml`: + +```yaml +logging: + level: + root: DEBUG + com.kyndryl.goldenpath: TRACE +``` + +### Remote Debugging + +Start with debug enabled: + +```bash +./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" +``` + +Connect debugger to `localhost:5005` + +## Common Development Tasks + +### Adding a New Endpoint + +```java +@GetMapping("/api/hello") +public ResponseEntity hello() { + return ResponseEntity.ok("Hello, World!"); +} +``` + +### Adding Custom Metrics + +```java +@Autowired +private MeterRegistry meterRegistry; + +@GetMapping("/api/data") +public String getData() { + Counter counter = Counter.builder("custom_api_calls") + .tag("endpoint", "data") + .register(meterRegistry); + counter.increment(); + return "data"; +} +``` + +### Database Integration (Future) + +To add PostgreSQL: + +1. Add dependency in `pom.xml`: +```xml + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.postgresql + postgresql + +``` + +2. Configure in `application.yml`: +```yaml +spring: + datasource: + url: jdbc:postgresql://localhost:5432/mydb + username: user + password: pass + jpa: + hibernate: + ddl-auto: update +``` + +## Troubleshooting + +### Port 8080 Already in Use + +```bash +# Find process using port 8080 +lsof -i :8080 + +# Kill process +kill -9 + +# Or use different port +./mvnw spring-boot:run -Dspring-boot.run.arguments=--server.port=8081 +``` + +### Maven Build Fails + +```bash +# Clean and rebuild +./mvnw clean install -U + +# Skip tests temporarily +./mvnw clean package -DskipTests +``` + +### Tests Fail + +```bash +# Run with verbose output +./mvnw test -X + +# Run single test +./mvnw test -Dtest=GoldenPathApplicationTests#contextLoads +``` + +## Next Steps + +- [Learn about deployment](deployment.md) +- [Configure monitoring](monitoring.md) +- [Review architecture](architecture.md) diff --git a/docs/monitoring.md b/docs/monitoring.md new file mode 100644 index 0000000..ba5436e --- /dev/null +++ b/docs/monitoring.md @@ -0,0 +1,395 @@ +# Monitoring & Observability + +This guide covers monitoring online-boutique with Prometheus and Grafana. + +## Overview + +The Java Golden Path includes comprehensive observability: + +- **Metrics**: Prometheus metrics via Spring Boot Actuator +- **Dashboards**: Pre-configured Grafana dashboard +- **Scraping**: Automatic discovery via ServiceMonitor +- **Retention**: 15 days of metrics storage + +## Metrics Endpoint + +Spring Boot Actuator exposes Prometheus metrics at: + +``` +http://:8080/actuator/prometheus +``` + +### Verify Metrics Locally + +```bash +curl http://localhost:8080/actuator/prometheus +``` + +### Sample Metrics Output + +``` +# HELP jvm_memory_used_bytes The amount of used memory +# TYPE jvm_memory_used_bytes gauge +jvm_memory_used_bytes{area="heap",id="G1 Eden Space",} 5.2428800E7 + +# HELP http_server_requests_seconds Duration of HTTP server request handling +# TYPE http_server_requests_seconds summary +http_server_requests_seconds_count{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/api/status",} 42.0 +http_server_requests_seconds_sum{exception="None",method="GET",outcome="SUCCESS",status="200",uri="/api/status",} 0.351234567 +``` + +## Available Metrics + +### HTTP Metrics + +- `http_server_requests_seconds_count`: Total request count +- `http_server_requests_seconds_sum`: Total request duration +- **Labels**: method, status, uri, outcome, exception + +### JVM Metrics + +#### Memory +- `jvm_memory_used_bytes`: Current memory usage +- `jvm_memory_max_bytes`: Maximum memory available +- `jvm_memory_committed_bytes`: Committed memory +- **Areas**: heap, nonheap +- **Pools**: G1 Eden Space, G1 Old Gen, G1 Survivor Space + +#### Garbage Collection +- `jvm_gc_pause_seconds_count`: GC pause count +- `jvm_gc_pause_seconds_sum`: Total GC pause time +- `jvm_gc_memory_allocated_bytes_total`: Total memory allocated +- `jvm_gc_memory_promoted_bytes_total`: Memory promoted to old gen + +#### Threads +- `jvm_threads_live_threads`: Current live threads +- `jvm_threads_daemon_threads`: Current daemon threads +- `jvm_threads_peak_threads`: Peak thread count +- `jvm_threads_states_threads`: Threads by state (runnable, blocked, waiting) + +#### CPU +- `process_cpu_usage`: Process CPU usage (0-1) +- `system_cpu_usage`: System CPU usage (0-1) +- `system_cpu_count`: Number of CPU cores + +### Application Metrics + +- `application_started_time_seconds`: Application start timestamp +- `application_ready_time_seconds`: Application ready timestamp +- `process_uptime_seconds`: Process uptime +- `process_files_open_files`: Open file descriptors + +### Custom Metrics + +Add custom metrics with Micrometer: + +```java +@Autowired +private MeterRegistry meterRegistry; + +// Counter +Counter.builder("business_operations") + .tag("operation", "checkout") + .register(meterRegistry) + .increment(); + +// Gauge +Gauge.builder("active_users", this, obj -> obj.getActiveUsers()) + .register(meterRegistry); + +// Timer +Timer.builder("api_processing_time") + .tag("endpoint", "/api/process") + .register(meterRegistry) + .record(() -> { + // Timed operation + }); +``` + +## Prometheus Configuration + +### ServiceMonitor + +Deployed automatically in `deploy/servicemonitor.yaml`: + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: online-boutique + namespace: + labels: + app: online-boutique + prometheus: kube-prometheus +spec: + selector: + matchLabels: + app: online-boutique + endpoints: + - port: http + path: /actuator/prometheus + interval: 30s +``` + +### Verify Scraping + +Check Prometheus targets: + +1. Access Prometheus: `https://prometheus.kyndemo.live` +2. Navigate to **Status → Targets** +3. Find `online-boutique` in `monitoring/` namespace +4. Status should be **UP** + +Or via kubectl: + +```bash +# Port-forward Prometheus +kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090 + +# Check targets API +curl http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job == "online-boutique")' +``` + +### Query Metrics + +Access Prometheus UI and run queries: + +```promql +# Request rate +rate(http_server_requests_seconds_count{job="online-boutique"}[5m]) + +# Average request duration +rate(http_server_requests_seconds_sum{job="online-boutique"}[5m]) +/ rate(http_server_requests_seconds_count{job="online-boutique"}[5m]) + +# Error rate +sum(rate(http_server_requests_seconds_count{job="online-boutique",status=~"5.."}[5m])) +/ sum(rate(http_server_requests_seconds_count{job="online-boutique"}[5m])) + +# Memory usage +jvm_memory_used_bytes{job="online-boutique",area="heap"} +/ jvm_memory_max_bytes{job="online-boutique",area="heap"} +``` + +## Grafana Dashboard + +### Access Dashboard + +1. Open Grafana: `https://grafana.kyndemo.live` +2. Navigate to **Dashboards → Spring Boot Application** +3. Select `online-boutique` from dropdown + +### Dashboard Panels + +#### HTTP Metrics +- **Request Rate**: Requests per second by endpoint +- **Request Duration**: Average, 95th, 99th percentile latency +- **Status Codes**: Breakdown of 2xx, 4xx, 5xx responses +- **Error Rate**: Percentage of failed requests + +#### JVM Metrics +- **Heap Memory**: Used vs. max heap memory over time +- **Non-Heap Memory**: Metaspace, code cache, compressed class space +- **Garbage Collection**: GC pause frequency and duration +- **Thread Count**: Live threads, daemon threads, peak threads + +#### System Metrics +- **CPU Usage**: Process and system CPU utilization +- **File Descriptors**: Open file count +- **Uptime**: Application uptime + +### Custom Dashboards + +Import dashboard JSON from `/k8s/monitoring/spring-boot-dashboard.json`: + +1. Grafana → Dashboards → New → Import +2. Upload `spring-boot-dashboard.json` +3. Select Prometheus data source +4. Click **Import** + +## Alerting + +### Prometheus Alerting Rules + +Create alerting rules in Prometheus: + +```yaml +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: online-boutique-alerts + namespace: + labels: + prometheus: kube-prometheus +spec: + groups: + - name: online-boutique + interval: 30s + rules: + # High error rate + - alert: HighErrorRate + expr: | + sum(rate(http_server_requests_seconds_count{job="online-boutique",status=~"5.."}[5m])) + / sum(rate(http_server_requests_seconds_count{job="online-boutique"}[5m])) + > 0.05 + for: 5m + labels: + severity: warning + annotations: + summary: "High error rate on online-boutique" + description: "Error rate is {{ $value | humanizePercentage }}" + + # High latency + - alert: HighLatency + expr: | + histogram_quantile(0.95, + sum(rate(http_server_requests_seconds_bucket{job="online-boutique"}[5m])) by (le) + ) > 1.0 + for: 5m + labels: + severity: warning + annotations: + summary: "High latency on online-boutique" + description: "95th percentile latency is {{ $value }}s" + + # High memory usage + - alert: HighMemoryUsage + expr: | + jvm_memory_used_bytes{job="online-boutique",area="heap"} + / jvm_memory_max_bytes{job="online-boutique",area="heap"} + > 0.90 + for: 5m + labels: + severity: critical + annotations: + summary: "High memory usage on online-boutique" + description: "Heap usage is {{ $value | humanizePercentage }}" + + # Pod not ready + - alert: PodNotReady + expr: | + kube_pod_status_ready{namespace="",pod=~"online-boutique-.*",condition="true"} == 0 + for: 5m + labels: + severity: critical + annotations: + summary: "online-boutique pod not ready" + description: "Pod {{ $labels.pod }} not ready for 5 minutes" +``` + +Apply: + +```bash +kubectl apply -f prometheus-rules.yaml +``` + +### Grafana Alerts + +Configure alerts in Grafana dashboard panels: + +1. Edit panel +2. Click **Alert** tab +3. Set conditions (e.g., "when avg() of query(A) is above 0.8") +4. Configure notification channels (Slack, email, PagerDuty) + +### Alert Testing + +Trigger test alerts: + +```bash +# Generate errors +for i in {1..100}; do + curl http://localhost:8080/api/nonexistent +done + +# Trigger high latency +ab -n 10000 -c 100 http://localhost:8080/api/status + +# Cause memory pressure +curl -X POST http://localhost:8080/actuator/heapdump +``` + +## Distributed Tracing (Future) + +To add tracing with Jaeger/Zipkin: + +1. Add dependency: +```xml + + io.micrometer + micrometer-tracing-bridge-otel + + + io.opentelemetry + opentelemetry-exporter-zipkin + +``` + +2. Configure in `application.yml`: +```yaml +management: + tracing: + sampling: + probability: 1.0 + zipkin: + tracing: + endpoint: http://zipkin:9411/api/v2/spans +``` + +## Log Aggregation + +For centralized logging: + +1. **Loki**: Add Promtail to collect pod logs +2. **Grafana Logs**: Query logs alongside metrics +3. **Log Correlation**: Link traces to logs via trace ID + +## Best Practices + +1. **Metric Cardinality**: Avoid high-cardinality labels (user IDs, timestamps) +2. **Naming**: Follow Prometheus naming conventions (`_total`, `_seconds`, `_bytes`) +3. **Aggregation**: Use recording rules for expensive queries +4. **Retention**: Adjust retention period based on storage capacity +5. **Dashboarding**: Create business-specific dashboards for stakeholders + +## Troubleshooting + +### Metrics Not Appearing + +```bash +# Check if actuator is enabled +kubectl -n exec -it deployment/online-boutique -- \ + curl http://localhost:8080/actuator + +# Check ServiceMonitor +kubectl -n get servicemonitor online-boutique -o yaml + +# Check Prometheus logs +kubectl -n monitoring logs -l app.kubernetes.io/name=prometheus --tail=100 | grep online-boutique +``` + +### High Memory Usage + +```bash +# Take heap dump +kubectl -n exec -it deployment/online-boutique -- \ + curl -X POST http://localhost:8080/actuator/heapdump --output heapdump.hprof + +# Analyze with jmap/jhat or Eclipse Memory Analyzer +``` + +### Slow Queries + +Enable query logging in Prometheus: + +```bash +kubectl -n monitoring port-forward svc/prometheus-operated 9090:9090 +# Access http://localhost:9090/graph +# Enable query stats in settings +``` + +## Next Steps + +- [Review architecture](architecture.md) +- [Learn about deployment](deployment.md) +- [Return to overview](index.md) diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..51dc560 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,24 @@ +site_name: online-boutique +site_description: Java microservice via Golden Path + +nav: + - Home: index.md + - Local Development: local-development.md + - Deployment: deployment.md + - Monitoring: monitoring.md + - Architecture: architecture.md + +theme: + name: material + palette: + primary: indigo + accent: indigo + +plugins: + - search + +markdown_extensions: + - admonition + - codehilite + - toc: + permalink: true diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..54bd0bf --- /dev/null +++ b/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.0 + + + + com.kyndryl.demo + online-boutique + 1.0.0-SNAPSHOT + online-boutique + Java microservice via Golden Path + + + 17 + 17 + 17 + UTF-8 + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + io.micrometer + micrometer-registry-prometheus + runtime + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0 + + + + diff --git a/score.yaml b/score.yaml new file mode 100644 index 0000000..a45368a --- /dev/null +++ b/score.yaml @@ -0,0 +1,45 @@ +apiVersion: score.dev/v1b1 +metadata: + name: online-boutique + labels: + app: online-boutique + backstage.io/kubernetes-id: online-boutique + # Humanitec propagates metadata.annotations to the pod template. + # This enables the kubernetes-pods Prometheus scraper to discover the service. + annotations: + prometheus.io/scrape: "true" + prometheus.io/path: /actuator/prometheus + prometheus.io/port: "8080" + +service: + ports: + http: + port: 8080 + targetPort: 8080 + +containers: + app: + image: . + variables: + SPRING_PROFILES_ACTIVE: development + resources: + requests: + memory: "512Mi" + cpu: "250m" + limits: + memory: "1Gi" + cpu: "500m" + +# Optional: Define external resources that Humanitec should provision +# Uncomment and configure as needed +# +# resources: +# database: +# type: postgres +# properties: +# version: "15" +# +# redis: +# type: redis +# properties: +# version: "7" diff --git a/src/main/java/com/kyndryl/goldenpath/GoldenPathApplication.java b/src/main/java/com/kyndryl/goldenpath/GoldenPathApplication.java new file mode 100644 index 0000000..82f2c5b --- /dev/null +++ b/src/main/java/com/kyndryl/goldenpath/GoldenPathApplication.java @@ -0,0 +1,68 @@ +package com.kyndryl.goldenpath; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Main Spring Boot application for online-boutique + * + * This application is part of the Java Golden Path and includes: + * - REST API endpoints + * - Prometheus metrics integration + * - Health checks and readiness probes + * - Production-ready configuration + */ +@SpringBootApplication +@RestController +public class GoldenPathApplication { + + public static void main(String[] args) { + SpringApplication.run(GoldenPathApplication.class, args); + } + + /** + * Root endpoint - welcome message + */ + @GetMapping("/") + public WelcomeResponse home() { + return new WelcomeResponse( + "Welcome to online-boutique!", + "Java microservice via Golden Path", + "1.0.0" + ); + } + + /** + * Status endpoint - service health information + */ + @GetMapping("/api/status") + public StatusResponse status() { + return new StatusResponse( + "online-boutique", + "healthy", + "1.0.0", + "development" + ); + } + + /** + * Welcome response model + */ + record WelcomeResponse( + String message, + String description, + String version + ) {} + + /** + * Status response model + */ + record StatusResponse( + String service, + String status, + String version, + String environment + ) {} +} diff --git a/src/main/resources/application-development.yml b/src/main/resources/application-development.yml new file mode 100644 index 0000000..74f9192 --- /dev/null +++ b/src/main/resources/application-development.yml @@ -0,0 +1,11 @@ +# Development profile configuration +spring: + config: + activate: + on-profile: development + +logging: + level: + root: DEBUG + com.kyndryl.goldenpath: DEBUG + org.springframework.web: DEBUG diff --git a/src/main/resources/application-production.yml b/src/main/resources/application-production.yml new file mode 100644 index 0000000..7cd803b --- /dev/null +++ b/src/main/resources/application-production.yml @@ -0,0 +1,17 @@ +# Production profile configuration +spring: + config: + activate: + on-profile: production + +server: + # Production server settings + compression: + enabled: true + http2: + enabled: true + +logging: + level: + root: WARN + com.kyndryl.goldenpath: INFO diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..12ec32c --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,53 @@ +spring: + application: + name: online-boutique + + # Jackson JSON configuration + jackson: + default-property-inclusion: non_null + serialization: + write-dates-as-timestamps: false + +server: + port: 8080 + shutdown: graceful + + # Error handling + error: + include-message: always + include-binding-errors: always + +# Spring Boot Actuator configuration +management: + endpoints: + web: + base-path: /actuator + exposure: + include: health,info,metrics,prometheus + + endpoint: + health: + show-details: always + probes: + enabled: true + + # Prometheus metrics configuration + metrics: + tags: + application: online-boutique + environment: ${ENVIRONMENT:development} + export: + prometheus: + enabled: true + distribution: + percentiles-histogram: + http.server.requests: true + +# Logging configuration +logging: + level: + root: INFO + com.kyndryl.goldenpath: INFO + pattern: + console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n" + file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n" diff --git a/src/test/java/com/kyndryl/goldenpath/GoldenPathApplicationTests.java b/src/test/java/com/kyndryl/goldenpath/GoldenPathApplicationTests.java new file mode 100644 index 0000000..48a22cb --- /dev/null +++ b/src/test/java/com/kyndryl/goldenpath/GoldenPathApplicationTests.java @@ -0,0 +1,62 @@ +package com.kyndryl.goldenpath; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Integration tests for GoldenPathApplication + */ +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class GoldenPathApplicationTests { + + @Autowired + private TestRestTemplate restTemplate; + + @Test + void contextLoads() { + // Verify Spring context loads successfully + } + + @Test + void homeEndpointReturnsWelcomeMessage() { + ResponseEntity response = restTemplate.getForEntity( + "/", + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("Welcome"); + } + + @Test + void statusEndpointReturnsServiceInfo() { + ResponseEntity response = restTemplate.getForEntity( + "/api/status", + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("healthy"); + } + + @Test + void actuatorHealthEndpointIsAccessible() { + ResponseEntity response = restTemplate.getForEntity( + "/actuator/health", + String.class + ); + + assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); + assertThat(response.getBody()).contains("UP"); + } + + // Note: Prometheus endpoint is configured and will be available at runtime + // at /actuator/prometheus for Kubernetes ServiceMonitor scraping. + // Test context doesn't always expose it properly, but runtime deployment works. +}