Add Docker build, K8s manifests, and CI/CD deploy pipeline
Some checks failed
CI / docker (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / build (push) Has been cancelled

- Dockerfile: multi-stage build with $BUILDPLATFORM for native Maven
  builds on ARM64 runners, amd64 runtime target. Passes REGISTRY_TOKEN
  build arg for cameleer3-common dependency resolution.
- K8s manifests: ClickHouse StatefulSet with init scripts ConfigMap,
  server Deployment + NodePort (30081)
- CI: docker job (QEMU + buildx cross-compile, registry cache,
  provenance=false, old image cleanup) + deploy job (kubectl)
- .dockerignore for build context optimization

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-12 19:01:23 +01:00
parent f9a35e1627
commit c228c3201b
5 changed files with 348 additions and 0 deletions

10
.dockerignore Normal file
View File

@@ -0,0 +1,10 @@
**/target/
.git/
.gitea/
.idea/
*.iml
docs/
*.md
!pom.xml
.planning/
.claude/

View File

@@ -3,6 +3,8 @@ name: CI
on: on:
push: push:
branches: [main] branches: [main]
tags-ignore:
- 'v*'
pull_request: pull_request:
branches: [main] branches: [main]
@@ -12,6 +14,10 @@ jobs:
container: container:
image: maven:3.9-eclipse-temurin-17 image: maven:3.9-eclipse-temurin-17
steps: steps:
- name: Install Node.js
run: |
apt-get update && apt-get install -y nodejs
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Configure Gitea Maven Registry - name: Configure Gitea Maven Registry
@@ -40,3 +46,101 @@ jobs:
- name: Build and Test - name: Build and Test
run: mvn clean verify --batch-mode run: mvn clean verify --batch-mode
docker:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
container:
image: docker:27
steps:
- name: Checkout
run: |
apk add --no-cache git
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Login to registry
run: echo "$REGISTRY_TOKEN" | docker login gitea.siegeln.net -u cameleer --password-stdin
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Set up QEMU for cross-platform builds
run: docker run --rm --privileged tonistiigi/binfmt --install all
- name: Build and push
run: |
docker buildx create --use --name cibuilder
docker buildx build --platform linux/amd64 \
--build-arg REGISTRY_TOKEN="$REGISTRY_TOKEN" \
-t gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }} \
-t gitea.siegeln.net/cameleer/cameleer3-server:latest \
--cache-from type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache \
--cache-to type=registry,ref=gitea.siegeln.net/cameleer/cameleer3-server:buildcache,mode=max \
--provenance=false \
--push .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Cleanup local Docker
run: docker system prune -af --filter "until=24h"
if: always()
- name: Cleanup old container images
run: |
apk add --no-cache curl jq
API="https://gitea.siegeln.net/api/v1"
AUTH="Authorization: token ${REGISTRY_TOKEN}"
CURRENT_SHA="${{ github.sha }}"
curl -sf -H "$AUTH" "$API/packages/cameleer/container/cameleer3-server" | \
jq -r '.[] | "\(.id) \(.version)"' | \
while read id version; do
if [ "$version" != "latest" ] && [ "$version" != "$CURRENT_SHA" ]; then
echo "Deleting old image tag: $version"
curl -sf -X DELETE -H "$AUTH" "$API/packages/cameleer/container/cameleer3-server/$version"
fi
done
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
if: always()
deploy:
needs: docker
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
container:
image: bitnami/kubectl:latest
steps:
- name: Checkout
run: |
git clone --depth=1 --branch=${GITHUB_REF_NAME} https://cameleer:${REGISTRY_TOKEN}@gitea.siegeln.net/${GITHUB_REPOSITORY}.git .
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
- name: Configure kubectl
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_B64" | base64 -d > ~/.kube/config
env:
KUBECONFIG_B64: ${{ secrets.KUBECONFIG_BASE64 }}
- name: Deploy
run: |
kubectl create namespace cameleer --dry-run=client -o yaml | kubectl apply -f -
kubectl create secret docker-registry gitea-registry \
--namespace=cameleer \
--docker-server=gitea.siegeln.net \
--docker-username=cameleer \
--docker-password="$REGISTRY_TOKEN" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl create secret generic cameleer-auth \
--namespace=cameleer \
--from-literal=CAMELEER_AUTH_TOKEN="$CAMELEER_AUTH_TOKEN" \
--dry-run=client -o yaml | kubectl apply -f -
kubectl apply -f deploy/clickhouse.yaml
kubectl -n cameleer rollout status statefulset/clickhouse --timeout=120s
kubectl apply -f deploy/server.yaml
kubectl -n cameleer set image deployment/cameleer3-server \
server=gitea.siegeln.net/cameleer/cameleer3-server:${{ github.sha }}
kubectl -n cameleer rollout status deployment/cameleer3-server --timeout=120s
env:
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
CAMELEER_AUTH_TOKEN: ${{ secrets.CAMELEER_AUTH_TOKEN }}

26
Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM --platform=$BUILDPLATFORM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /build
# Configure Gitea Maven Registry for cameleer3-common dependency
ARG REGISTRY_TOKEN
RUN mkdir -p ~/.m2 && \
echo '<settings><servers><server><id>gitea</id><username>cameleer</username><password>'${REGISTRY_TOKEN}'</password></server></servers></settings>' > ~/.m2/settings.xml
COPY pom.xml .
COPY cameleer3-server-core/pom.xml cameleer3-server-core/
COPY cameleer3-server-app/pom.xml cameleer3-server-app/
# Cache deps — only re-downloaded when POMs change
RUN mvn dependency:go-offline -B || true
COPY . .
RUN mvn clean package -DskipTests -B
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /build/cameleer3-server-app/target/cameleer3-server-app-*.jar /app/server.jar
ENV SPRING_DATASOURCE_URL=jdbc:ch://clickhouse:8123/cameleer3
ENV SPRING_DATASOURCE_USERNAME=cameleer
ENV SPRING_DATASOURCE_PASSWORD=cameleer_dev
EXPOSE 8081
ENTRYPOINT exec java -jar /app/server.jar

153
deploy/clickhouse.yaml Normal file
View File

@@ -0,0 +1,153 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: clickhouse-init
namespace: cameleer
data:
01-schema.sql: |
CREATE TABLE IF NOT EXISTS route_executions (
execution_id String,
route_id LowCardinality(String),
agent_id LowCardinality(String),
status LowCardinality(String),
start_time DateTime64(3, 'UTC'),
end_time Nullable(DateTime64(3, 'UTC')),
duration_ms UInt64,
correlation_id String,
exchange_id String,
error_message String DEFAULT '',
error_stacktrace String DEFAULT '',
processor_ids Array(String),
processor_types Array(LowCardinality(String)),
processor_starts Array(DateTime64(3, 'UTC')),
processor_ends Array(DateTime64(3, 'UTC')),
processor_durations Array(UInt64),
processor_statuses Array(LowCardinality(String)),
server_received_at DateTime64(3, 'UTC') DEFAULT now64(3, 'UTC'),
INDEX idx_correlation correlation_id TYPE bloom_filter GRANULARITY 4,
INDEX idx_error error_message TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(start_time)
ORDER BY (agent_id, status, start_time, execution_id)
TTL toDateTime(start_time) + toIntervalDay(30)
SETTINGS ttl_only_drop_parts = 1;
CREATE TABLE IF NOT EXISTS route_diagrams (
content_hash String,
route_id LowCardinality(String),
agent_id LowCardinality(String),
definition String,
created_at DateTime64(3, 'UTC') DEFAULT now64(3, 'UTC')
)
ENGINE = ReplacingMergeTree(created_at)
ORDER BY (content_hash);
CREATE TABLE IF NOT EXISTS agent_metrics (
agent_id LowCardinality(String),
collected_at DateTime64(3, 'UTC'),
metric_name LowCardinality(String),
metric_value Float64,
tags Map(String, String),
server_received_at DateTime64(3, 'UTC') DEFAULT now64(3, 'UTC')
)
ENGINE = MergeTree()
PARTITION BY toYYYYMMDD(collected_at)
ORDER BY (agent_id, metric_name, collected_at)
TTL toDateTime(collected_at) + toIntervalDay(30)
SETTINGS ttl_only_drop_parts = 1;
02-search-columns.sql: |
ALTER TABLE route_executions
ADD COLUMN IF NOT EXISTS exchange_bodies String DEFAULT '',
ADD COLUMN IF NOT EXISTS exchange_headers String DEFAULT '',
ADD COLUMN IF NOT EXISTS processor_depths Array(UInt16) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_parent_indexes Array(Int32) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_error_messages Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_error_stacktraces Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_input_bodies Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_output_bodies Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_input_headers Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_output_headers Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS processor_diagram_node_ids Array(String) DEFAULT [],
ADD COLUMN IF NOT EXISTS diagram_content_hash String DEFAULT '';
ALTER TABLE route_executions
ADD INDEX IF NOT EXISTS idx_exchange_bodies exchange_bodies TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4,
ADD INDEX IF NOT EXISTS idx_exchange_headers exchange_headers TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4;
ALTER TABLE route_executions
ADD INDEX IF NOT EXISTS idx_error_stacktrace error_stacktrace TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 4;
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: clickhouse
namespace: cameleer
spec:
serviceName: clickhouse
replicas: 1
selector:
matchLabels:
app: clickhouse
template:
metadata:
labels:
app: clickhouse
spec:
containers:
- name: clickhouse
image: clickhouse/clickhouse-server:25.3
ports:
- containerPort: 8123
name: http
- containerPort: 9000
name: native
env:
- name: CLICKHOUSE_USER
value: cameleer
- name: CLICKHOUSE_PASSWORD
value: cameleer_dev
- name: CLICKHOUSE_DB
value: cameleer3
volumeMounts:
- name: data
mountPath: /var/lib/clickhouse
- name: init-scripts
mountPath: /docker-entrypoint-initdb.d
resources:
requests:
memory: "512Mi"
cpu: "200m"
limits:
memory: "1Gi"
cpu: "1000m"
volumes:
- name: init-scripts
configMap:
name: clickhouse-init
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
name: clickhouse
namespace: cameleer
spec:
clusterIP: None
selector:
app: clickhouse
ports:
- port: 8123
targetPort: 8123
name: http
- port: 9000
targetPort: 9000
name: native

55
deploy/server.yaml Normal file
View File

@@ -0,0 +1,55 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: cameleer3-server
namespace: cameleer
spec:
replicas: 1
selector:
matchLabels:
app: cameleer3-server
template:
metadata:
labels:
app: cameleer3-server
spec:
imagePullSecrets:
- name: gitea-registry
containers:
- name: server
image: gitea.siegeln.net/cameleer/cameleer3-server:latest
ports:
- containerPort: 8081
env:
- name: SPRING_DATASOURCE_URL
value: "jdbc:ch://clickhouse:8123/cameleer3"
- name: SPRING_DATASOURCE_USERNAME
value: "cameleer"
- name: SPRING_DATASOURCE_PASSWORD
value: "cameleer_dev"
- name: CAMELEER_AUTH_TOKEN
valueFrom:
secretKeyRef:
name: cameleer-auth
key: CAMELEER_AUTH_TOKEN
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: cameleer3-server
namespace: cameleer
spec:
type: NodePort
selector:
app: cameleer3-server
ports:
- port: 8081
targetPort: 8081
nodePort: 30081