adding kustomization
This commit is contained in:
@@ -7,6 +7,8 @@ spec:
|
|||||||
destinations:
|
destinations:
|
||||||
- server: https://kubernetes.default.svc
|
- server: https://kubernetes.default.svc
|
||||||
namespace: ai
|
namespace: ai
|
||||||
|
- server: https://kubernetes.default.svc
|
||||||
|
namespace: argo
|
||||||
# # only add if need to deploy into argocd (ehhhhh)
|
# # only add if need to deploy into argocd (ehhhhh)
|
||||||
# - server: https://kubernetes.default.svc
|
# - server: https://kubernetes.default.svc
|
||||||
# namespace: argocd
|
# namespace: argocd
|
||||||
@@ -27,8 +29,6 @@ spec:
|
|||||||
repoURL: https://git.ion606.com/ion606/ollama-plus
|
repoURL: https://git.ion606.com/ion606/ollama-plus
|
||||||
targetRevision: argo
|
targetRevision: argo
|
||||||
path: apps/children
|
path: apps/children
|
||||||
directory:
|
|
||||||
recurse: true
|
|
||||||
syncPolicy:
|
syncPolicy:
|
||||||
automated:
|
automated:
|
||||||
prune: true
|
prune: true
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: argo-templates
|
||||||
|
namespace: ai
|
||||||
|
spec:
|
||||||
|
project: ai-stack
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: argo
|
||||||
|
source:
|
||||||
|
repoURL: https://git.ion606.com/ion606/ollama-plus
|
||||||
|
targetRevision: main
|
||||||
|
path: apps/argo-templates
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: ollama-scheduler
|
||||||
|
namespace: ai
|
||||||
|
labels:
|
||||||
|
repo.ion606.com/ollama-plus: "true"
|
||||||
|
spec:
|
||||||
|
project: ai-stack
|
||||||
|
destination:
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
namespace: argo
|
||||||
|
source:
|
||||||
|
repoURL: https://git.ion606.com/ion606/ollama-plus
|
||||||
|
targetRevision: main
|
||||||
|
path: manifests/argo-ollama-scheduler
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
prune: true
|
||||||
|
selfHeal: true
|
||||||
@@ -3,6 +3,8 @@ kind: Application
|
|||||||
metadata:
|
metadata:
|
||||||
name: coderunner
|
name: coderunner
|
||||||
namespace: ai
|
namespace: ai
|
||||||
|
labels:
|
||||||
|
repo.ion606.com/ollama-plus: "true"
|
||||||
spec:
|
spec:
|
||||||
project: ai-stack
|
project: ai-stack
|
||||||
destination:
|
destination:
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- argo-ollama-scheduler.yaml
|
||||||
|
- coderunner.yaml
|
||||||
|
- tools.yaml
|
||||||
|
- rag-server.yaml
|
||||||
|
- openwebui.yaml
|
||||||
|
- postgresql.yaml
|
||||||
|
- searxng.yaml
|
||||||
|
- browser.yaml
|
||||||
|
|
||||||
|
generatorOptions:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
|
||||||
|
configMapGenerator:
|
||||||
|
- name: ollama-plus-revs
|
||||||
|
literals:
|
||||||
|
- targetRevision=main
|
||||||
|
|
||||||
|
# Inject targetRevision from the ConfigMap into apps (kill me)
|
||||||
|
replacements:
|
||||||
|
- source:
|
||||||
|
kind: ConfigMap
|
||||||
|
name: ollama-plus-revs
|
||||||
|
fieldPath: data.targetRevision
|
||||||
|
targets:
|
||||||
|
- select:
|
||||||
|
kind: Application
|
||||||
|
labelSelector: repo.ion606.com/ollama-plus=true
|
||||||
|
fieldPaths:
|
||||||
|
- spec.source.targetRevision
|
||||||
|
options:
|
||||||
|
create: true
|
||||||
@@ -3,6 +3,8 @@ kind: Application
|
|||||||
metadata:
|
metadata:
|
||||||
name: rag-server
|
name: rag-server
|
||||||
namespace: ai
|
namespace: ai
|
||||||
|
labels:
|
||||||
|
repo.ion606.com/ollama-plus: "true"
|
||||||
spec:
|
spec:
|
||||||
project: ai-stack
|
project: ai-stack
|
||||||
destination:
|
destination:
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ kind: Application
|
|||||||
metadata:
|
metadata:
|
||||||
name: tools
|
name: tools
|
||||||
namespace: ai
|
namespace: ai
|
||||||
|
labels:
|
||||||
|
repo.ion606.com/ollama-plus: "true"
|
||||||
spec:
|
spec:
|
||||||
project: ai-stack
|
project: ai-stack
|
||||||
destination:
|
destination:
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels: { app: schedules-api }
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels: { app: schedules-api }
|
||||||
|
spec:
|
||||||
|
serviceAccountName: schedules-api
|
||||||
|
containers:
|
||||||
|
- name: schedules-api
|
||||||
|
# TODO: build & push your image, then update below
|
||||||
|
image: ghcr.io/your-org/schedules-api:0.1.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- { name: PORT, value: "3000" }
|
||||||
|
- { name: NS, value: "argo" }
|
||||||
|
ports:
|
||||||
|
- { name: http, containerPort: 3000 }
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket: { port: 3000 }
|
||||||
|
initialDelaySeconds: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket: { port: 3000 }
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 20
|
||||||
|
resources:
|
||||||
|
requests: { cpu: "50m", memory: "64Mi" }
|
||||||
|
limits: { cpu: "200m", memory: "256Mi" }
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
spec:
|
||||||
|
selector: { app: schedules-api }
|
||||||
|
ports:
|
||||||
|
- { name: http, port: 3000, targetPort: 3000 }
|
||||||
|
type: ClusterIP
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["argoproj.io"]
|
||||||
|
resources: ["cronworkflows"]
|
||||||
|
verbs: ["create","get","list","watch","update","patch","delete"]
|
||||||
|
- apiGroups: ["argoproj.io"]
|
||||||
|
resources: ["workflows"]
|
||||||
|
verbs: ["create","get","list"]
|
||||||
|
- apiGroups: ["argoproj.io"]
|
||||||
|
resources: ["workflowtemplates"]
|
||||||
|
verbs: ["get","list"]
|
||||||
|
---
|
||||||
|
# If you need ClusterWorkflowTemplate support, create this ClusterRole and a ClusterRoleBinding
|
||||||
|
# with subject serviceAccountName: schedules-api, namespace: argo
|
||||||
|
# apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
# kind: ClusterRole
|
||||||
|
# metadata:
|
||||||
|
# name: schedules-api-cwft-read
|
||||||
|
# rules:
|
||||||
|
# - apiGroups: ["argoproj.io"]
|
||||||
|
# resources: ["clusterworkflowtemplates"]
|
||||||
|
# verbs: ["get","list"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: schedules-api
|
||||||
|
namespace: argo
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: schedules-api
|
||||||
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: WorkflowTemplate
|
||||||
|
metadata:
|
||||||
|
name: hello-template
|
||||||
|
namespace: argo
|
||||||
|
spec:
|
||||||
|
entrypoint: run
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- { name: message, value: "hello from argo" }
|
||||||
|
templates:
|
||||||
|
- name: run
|
||||||
|
container:
|
||||||
|
image: alpine:3.19
|
||||||
|
command: ["/bin/sh","-lc"]
|
||||||
|
args: ["echo \"{{workflow.parameters.message}}\""]
|
||||||
|
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: WorkflowTemplate
|
||||||
|
metadata:
|
||||||
|
name: ollama-job-template
|
||||||
|
namespace: argo
|
||||||
|
spec:
|
||||||
|
entrypoint: run
|
||||||
|
arguments:
|
||||||
|
parameters:
|
||||||
|
- { name: task, value: "quick-check" }
|
||||||
|
- { name: prompt, value: "reindex embeddings" }
|
||||||
|
templates:
|
||||||
|
- name: run
|
||||||
|
container:
|
||||||
|
# Replace with an image that can reach your Ollama endpoint
|
||||||
|
image: curlimages/curl:8.9.0
|
||||||
|
command: ["/bin/sh","-lc"]
|
||||||
|
args:
|
||||||
|
- >-
|
||||||
|
echo "task: {{workflow.parameters.task}}";
|
||||||
|
echo "prompt: {{workflow.parameters.prompt}}";
|
||||||
|
# Example call (adjust host/port or service DNS):
|
||||||
|
# curl -s http://ollama.ai.svc.cluster.local:11434/api/generate \
|
||||||
|
# -H 'content-type: application/json' \
|
||||||
|
# -d '{"model":"llama3.1","prompt":"{{workflow.parameters.prompt}}"}' | tee /tmp/out.json;
|
||||||
|
echo done.
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
npm-cache
|
||||||
|
bun.lockb
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
FROM oven/bun:1 as base
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# prod deps
|
||||||
|
COPY package.json ./package.json
|
||||||
|
RUN bun install --ci --production
|
||||||
|
|
||||||
|
COPY server.mjs ./server.mjs
|
||||||
|
|
||||||
|
USER bun
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
CMD ["bun", "run", "server.mjs"]
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"name": "schedules-api",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "bun run server.mjs",
|
||||||
|
"dev": "bun run --hot server.mjs"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@kubernetes/client-node": "^0.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
// bun run server.mjs
|
||||||
|
// tiny schedules api to manage argo cronworkflows/workflows via k8s CRDs
|
||||||
|
// comments intentionally lowercase per original style
|
||||||
|
|
||||||
|
import http from 'http'
|
||||||
|
import { KubeConfig, CustomObjectsApi } from '@kubernetes/client-node'
|
||||||
|
|
||||||
|
const GROUP = 'argoproj.io'
|
||||||
|
const VERSION = 'v1alpha1'
|
||||||
|
const CRON_PLURAL = 'cronworkflows'
|
||||||
|
const WF_PLURAL = 'workflows'
|
||||||
|
const NAMESPACE = process.env.NS || 'argo'
|
||||||
|
|
||||||
|
// load cluster credentials (or fallback to local kubeconfig for dev)
|
||||||
|
const kc = new KubeConfig()
|
||||||
|
try { kc.loadFromCluster() } catch { kc.loadFromDefault() }
|
||||||
|
const co = kc.makeApiClient(CustomObjectsApi)
|
||||||
|
|
||||||
|
// helper: build cron string from an iso timestamp in a tz
|
||||||
|
const cronFromISO = (iso, tz = 'America/New_York') => {
|
||||||
|
const dt = new Date(iso)
|
||||||
|
const parts = new Intl.DateTimeFormat('en-US', {
|
||||||
|
timeZone: tz, year: 'numeric', month: 'numeric', day: 'numeric',
|
||||||
|
hour: 'numeric', minute: '2-digit', hour12: false
|
||||||
|
}).formatToParts(dt).reduce((a, p) => (a[p.type] = p.value, a), {})
|
||||||
|
const m = Number(parts.month), d = Number(parts.day), h = Number(parts.hour), min = Number(parts.minute)
|
||||||
|
return `${min} ${h} ${d} ${m} *`
|
||||||
|
}
|
||||||
|
|
||||||
|
// create or update a cronworkflow that runs a workflowtemplate
|
||||||
|
async function upsertCronWorkflow({
|
||||||
|
name, when, tz = 'America/New_York', oneShot = false,
|
||||||
|
template = { name: '', clusterScope: false },
|
||||||
|
parameters = {}, entrypoint
|
||||||
|
}) {
|
||||||
|
const schedule = when.cron ?? cronFromISO(when.iso, tz)
|
||||||
|
const args = Object.entries(parameters).map(([name, value]) => ({ name, value }))
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
apiVersion: `${GROUP}/${VERSION}`,
|
||||||
|
kind: 'CronWorkflow',
|
||||||
|
metadata: { name },
|
||||||
|
spec: {
|
||||||
|
timezone: tz,
|
||||||
|
schedules: [schedule],
|
||||||
|
concurrencyPolicy: 'Forbid',
|
||||||
|
...(oneShot ? { stopStrategy: { expression: 'cronworkflow.succeeded >= 1' } } : {}),
|
||||||
|
workflowSpec: {
|
||||||
|
...(entrypoint ? { entrypoint } : {}),
|
||||||
|
arguments: args.length ? { parameters: args } : undefined,
|
||||||
|
workflowTemplateRef: {
|
||||||
|
name: template.name,
|
||||||
|
...(template.clusterScope ? { clusterScope: true } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try patch, else create
|
||||||
|
try {
|
||||||
|
await co.patchNamespacedCustomObject(
|
||||||
|
GROUP, VERSION, NAMESPACE, CRON_PLURAL, name, body,
|
||||||
|
undefined, undefined, undefined,
|
||||||
|
{ headers: { 'content-type': 'application/merge-patch+json' } }
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
await co.createNamespacedCustomObject(GROUP, VERSION, NAMESPACE, CRON_PLURAL, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run immediately (no schedule) by creating a workflow from the same template
|
||||||
|
async function runNow({ name, template, parameters = {}, entrypoint }) {
|
||||||
|
const args = Object.entries(parameters).map(([name, value]) => ({ name, value }))
|
||||||
|
const wf = {
|
||||||
|
apiVersion: `${GROUP}/${VERSION}`,
|
||||||
|
kind: 'Workflow',
|
||||||
|
metadata: { generateName: `${name}-` },
|
||||||
|
spec: {
|
||||||
|
...(entrypoint ? { entrypoint } : {}),
|
||||||
|
arguments: args.length ? { parameters: args } : undefined,
|
||||||
|
workflowTemplateRef: {
|
||||||
|
name: template.name,
|
||||||
|
...(template.clusterScope ? { clusterScope: true } : {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await co.createNamespacedCustomObject(GROUP, VERSION, NAMESPACE, WF_PLURAL, wf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// tiny http api
|
||||||
|
const server = http.createServer(async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.method === 'POST' && req.url === '/schedules') {
|
||||||
|
const input = JSON.parse(await new Promise(r => {
|
||||||
|
let d = ''; req.on('data', c => d += c); req.on('end', () => r(d))
|
||||||
|
}))
|
||||||
|
await upsertCronWorkflow(input)
|
||||||
|
res.writeHead(201).end(JSON.stringify({ ok: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (req.method === 'POST' && req.url === '/run-now') {
|
||||||
|
const input = JSON.parse(await new Promise(r => {
|
||||||
|
let d = ''; req.on('data', c => d += c); req.on('end', () => r(d))
|
||||||
|
}))
|
||||||
|
await runNow(input)
|
||||||
|
res.writeHead(201).end(JSON.stringify({ ok: true }))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (req.method === 'DELETE' && req.url?.startsWith('/schedules/')) {
|
||||||
|
const name = decodeURIComponent(req.url.split('/').pop())
|
||||||
|
await co.deleteNamespacedCustomObject(GROUP, VERSION, NAMESPACE, CRON_PLURAL, name)
|
||||||
|
res.writeHead(204).end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
res.writeHead(404).end('not found')
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(500).end(JSON.stringify({ ok: false, error: e.message }))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const port = Number(process.env.PORT) || 3000
|
||||||
|
server.listen(port, () => console.log(`schedules api listening on :${port}`))
|
||||||
|
|
||||||
@@ -13,3 +13,7 @@ docker push ion606/rag-server:latest;
|
|||||||
# tools
|
# tools
|
||||||
docker build -t ion606/tools:latest ./tools;
|
docker build -t ion606/tools:latest ./tools;
|
||||||
docker push ion606/tools:latest;
|
docker push ion606/tools:latest;
|
||||||
|
|
||||||
|
# scheduling
|
||||||
|
docker build -t ion606/ollama-scheduler:latest ./scheduler;
|
||||||
|
docker push ion606/ollama-scheduler:latest;
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ minikube addons enable ingress-dns;
|
|||||||
# namespaces
|
# namespaces
|
||||||
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -;
|
kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f -;
|
||||||
kubectl create namespace ai --dry-run=client -o yaml | kubectl apply -f -;
|
kubectl create namespace ai --dry-run=client -o yaml | kubectl apply -f -;
|
||||||
|
# argo workflows namespace (for cronworkflows/workflows + templates)
|
||||||
|
kubectl create namespace argo --dry-run=client -o yaml | kubectl apply -f -;
|
||||||
|
|
||||||
# install argo cd (stable manifest)
|
# install argo cd (stable manifest)
|
||||||
# https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/
|
# https://argo-cd.readthedocs.io/en/stable/operator-manual/installation/
|
||||||
|
|||||||
Reference in New Issue
Block a user