initial commit
Change-Id: I9c68c43e939d2c1a3b95a68b71ecc5ba861a4df5
This commit is contained in:
141
.gitea/workflows/build-push.yml
Normal file
141
.gitea/workflows/build-push.yml
Normal file
@@ -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
|
||||||
196
.gitea/workflows/deploy-humanitec.yml
Normal file
196
.gitea/workflows/deploy-humanitec.yml
Normal file
@@ -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
|
||||||
|
|
||||||
107
.gitea/workflows/techdocs.yml
Normal file
107
.gitea/workflows/techdocs.yml
Normal file
@@ -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"
|
||||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -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/
|
||||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@@ -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"]
|
||||||
252
README.md
Normal file
252
README.md
Normal file
@@ -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
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
37
catalog-info.yaml
Normal file
37
catalog-info.yaml
Normal file
@@ -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
|
||||||
98
deploy/deployment.yaml
Normal file
98
deploy/deployment.yaml
Normal file
@@ -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
|
||||||
18
deploy/kustomization.yaml
Normal file
18
deploy/kustomization.yaml
Normal file
@@ -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
|
||||||
18
deploy/service.yaml
Normal file
18
deploy/service.yaml
Normal file
@@ -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
|
||||||
16
deploy/servicemonitor.yaml
Normal file
16
deploy/servicemonitor.yaml
Normal file
@@ -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
|
||||||
521
docs/architecture.md
Normal file
521
docs/architecture.md
Normal file
@@ -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<Map<String, String>> 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)
|
||||||
384
docs/deployment.md
Normal file
384
docs/deployment.md
Normal file
@@ -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 <DEPLOYMENT_ID> \
|
||||||
|
--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 <PREVIOUS_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 <POD_NAME>
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
kubectl -n logs <POD_NAME>
|
||||||
|
|
||||||
|
# Check previous container logs (if restarting)
|
||||||
|
kubectl -n logs <POD_NAME> --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 <DEPLOYMENT_ID> \
|
||||||
|
--org kyn-cjot \
|
||||||
|
--app online-boutique \
|
||||||
|
--env development
|
||||||
|
|
||||||
|
# View error logs
|
||||||
|
humctl logs \
|
||||||
|
--org kyn-cjot \
|
||||||
|
--app online-boutique \
|
||||||
|
--env development \
|
||||||
|
--deployment-id <DEPLOYMENT_ID>
|
||||||
|
|
||||||
|
# Cancel stuck deployment
|
||||||
|
humctl delete deployment <DEPLOYMENT_ID> \
|
||||||
|
--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 <POD_NAME> | 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)
|
||||||
130
docs/index.md
Normal file
130
docs/index.md
Normal file
@@ -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)
|
||||||
279
docs/local-development.md
Normal file
279
docs/local-development.md
Normal file
@@ -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
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
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<String> 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
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
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 <PID>
|
||||||
|
|
||||||
|
# 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)
|
||||||
395
docs/monitoring.md
Normal file
395
docs/monitoring.md
Normal file
@@ -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://<pod-ip>: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
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-tracing-bridge-otel</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.opentelemetry</groupId>
|
||||||
|
<artifactId>opentelemetry-exporter-zipkin</artifactId>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
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)
|
||||||
24
mkdocs.yml
Normal file
24
mkdocs.yml
Normal file
@@ -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
|
||||||
79
pom.xml
Normal file
79
pom.xml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
|
||||||
|
https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>com.kyndryl.demo</groupId>
|
||||||
|
<artifactId>online-boutique</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
<name>online-boutique</name>
|
||||||
|
<description>Java microservice via Golden Path</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<maven.compiler.source>17</maven.compiler.source>
|
||||||
|
<maven.compiler.target>17</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring Boot Web -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Spring Boot Actuator (Health checks, metrics) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Prometheus metrics -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.micrometer</groupId>
|
||||||
|
<artifactId>micrometer-registry-prometheus</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.11.0</version>
|
||||||
|
<configuration>
|
||||||
|
<release>17</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<version>3.0.0</version>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
45
score.yaml
Normal file
45
score.yaml
Normal file
@@ -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"
|
||||||
@@ -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
|
||||||
|
) {}
|
||||||
|
}
|
||||||
11
src/main/resources/application-development.yml
Normal file
11
src/main/resources/application-development.yml
Normal file
@@ -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
|
||||||
17
src/main/resources/application-production.yml
Normal file
17
src/main/resources/application-production.yml
Normal file
@@ -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
|
||||||
53
src/main/resources/application.yml
Normal file
53
src/main/resources/application.yml
Normal file
@@ -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"
|
||||||
@@ -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<String> response = restTemplate.getForEntity(
|
||||||
|
"/",
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).contains("Welcome");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void statusEndpointReturnsServiceInfo() {
|
||||||
|
ResponseEntity<String> response = restTemplate.getForEntity(
|
||||||
|
"/api/status",
|
||||||
|
String.class
|
||||||
|
);
|
||||||
|
|
||||||
|
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||||
|
assertThat(response.getBody()).contains("healthy");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void actuatorHealthEndpointIsAccessible() {
|
||||||
|
ResponseEntity<String> 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.
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user