Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
6145e26
docs: add kubernetes-agent solution design spec
jeff-schnitter Aug 25, 2026
3e30d71
docs: resolve open questions in kubernetes-agent solution spec
jeff-schnitter Aug 25, 2026
9f684e7
docs: add kubernetes-agent solution implementation plan
jeff-schnitter Aug 25, 2026
b728cea
feat: add kubernetes-agent solution catalog entity and demo manifests
jeff-schnitter Aug 25, 2026
6b92211
feat: bundle k8s-agent helm chart in kubernetes-agent solution
jeff-schnitter Aug 25, 2026
7ce6186
feat: add kubernetes-agent solution post-install setup script
jeff-schnitter Aug 25, 2026
9999d2e
feat: add kubernetes-agent Codespace devcontainer
jeff-schnitter Aug 25, 2026
b1656cd
docs: add kubernetes-agent solution README
jeff-schnitter Aug 25, 2026
e110b76
fix: use explicit deployment name for kubectl rollout commands
jeff-schnitter Aug 25, 2026
4506243
chore: add README frontmatter with solution name and description
jeff-schnitter Aug 25, 2026
4713f00
chore: set default cluster name to cortex-demo in helm values
jeff-schnitter Aug 25, 2026
3397bd4
chore: sync cluster name default to cortex-demo in setup prompt
jeff-schnitter Aug 25, 2026
63041ec
chore: add k8s prerequisites doc link to GHCR_TOKEN prompt and README
jeff-schnitter Aug 25, 2026
adf9edd
chore: clarify GHCR_TOKEN is provided by Cortex Customer Engineering
jeff-schnitter Aug 25, 2026
2658b43
chore: clarify kind runs in GitHub Codespace, add kind docs link
jeff-schnitter Aug 25, 2026
e5fbfe5
fix: add preflight cluster connectivity check with Codespace guidance
jeff-schnitter Aug 25, 2026
e95bdac
feat: support both GitHub Codespace and existing cluster deployment p…
jeff-schnitter Aug 25, 2026
4c89441
chore: pin Codespace creation to current feature branch
jeff-schnitter Aug 26, 2026
0286e0e
fix: specify machine type for gh codespace create to avoid no-termina…
jeff-schnitter Sep 2, 2026
caae054
fix: use sudo+arch-detect for kind install in onCreate.sh; clarify Co…
jeff-schnitter Sep 2, 2026
a4aee9a
chore: add phased status messages during Codespace/kind cluster wait
jeff-schnitter Sep 2, 2026
48096c6
fix: move kubectl/helm install into onCreate.sh, remove kubectl-helm-…
jeff-schnitter Sep 2, 2026
dbf8acd
fix: verify saved Codespace exists before reusing, create new one if …
jeff-schnitter Sep 2, 2026
af160ed
chore: save GHCR_TOKEN between runs using hidden=True
jeff-schnitter Sep 2, 2026
4ac61c8
chore: increase Codespace timeout to 15 min, update wait message to r…
jeff-schnitter Sep 2, 2026
dffa45f
fix: poll kubectl readiness when reusing existing Codespace
jeff-schnitter Sep 2, 2026
9860560
fix: log onCreate.sh output and improve timeout error message
jeff-schnitter Sep 2, 2026
d087cd6
fix: detect onCreate.sh failure immediately instead of timing out
jeff-schnitter Sep 2, 2026
7c5d673
fix: remove cortexapps-cli install from onCreate.sh (python3 not found)
jeff-schnitter Sep 2, 2026
37feda0
fix: auto-fetch and print onCreate.log on failure instead of asking user
jeff-schnitter Sep 2, 2026
501a2fd
fix: give Phase 2 its own deadline; add sshd feature for early SSH ac…
jeff-schnitter Sep 2, 2026
86de516
fix: use explicit kubeconfig path in poll; use login shell for _run_r…
jeff-schnitter Sep 3, 2026
31a4938
fix: replace bash -c poll script with simple SSH commands
jeff-schnitter Sep 3, 2026
e22aa9f
fix: use gh codespace cp to upload scripts instead of bash -c via SSH
jeff-schnitter Sep 3, 2026
019da91
fix: pipe script via SSH stdin to tee instead of using gh codespace cp
jeff-schnitter Sep 3, 2026
514a151
fix: chmod helm world-executable after install (get-helm-3 sets 750)
jeff-schnitter Sep 3, 2026
92d9b93
fix: use --server-side apply for Argo Rollouts CRD
jeff-schnitter Sep 3, 2026
a88d3ea
fix: correct post-install URL to /admin/resources?tag=demo-kubernetes
jeff-schnitter Sep 3, 2026
dab81f0
docs: add architecture diagram; fix timing and URL in README
jeff-schnitter Sep 3, 2026
b0741fa
docs: add Next Steps section for rolling out to real clusters
jeff-schnitter Sep 3, 2026
e943790
docs: add troubleshooting note for K8s metadata label customization
jeff-schnitter Sep 4, 2026
dd27b24
fix: replace Mermaid with ASCII art; rename sections for CLI menu
jeff-schnitter Sep 7, 2026
de6f218
docs: fix ASCII art spacing in kubernetes-agent data model diagram
jeff-schnitter Sep 7, 2026
11d24fb
Merge main into worktree-kubernetes-agent-solution
jeff-schnitter Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .devcontainer/kubernetes-agent/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "Cortex Kubernetes Agent Demo",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu-24.04",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/sshd:1": {}
},
"onCreateCommand": "bash .devcontainer/kubernetes-agent/onCreate.sh",
"remoteEnv": {
"CORTEX_API_KEY": "${localEnv:CORTEX_API_KEY}",
"GHCR_TOKEN": "${localEnv:GHCR_TOKEN}"
},
"postCreateMessage": "kind cluster is ready. Run cortex solutions post-install -s kubernetes-agent from your local machine."
}
38 changes: 38 additions & 0 deletions .devcontainer/kubernetes-agent/onCreate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
set -euo pipefail

# Log all output so failures are diagnosable: cat /tmp/onCreate.log
exec > >(tee /tmp/onCreate.log) 2>&1

# Write a failure sentinel on non-zero exit so setup.py can detect it fast
_on_exit() {
local rc=$?
if [[ $rc -ne 0 ]]; then
echo "$rc" > /tmp/onCreate.failed
fi
}
trap _on_exit EXIT

ARCH=$(uname -m)
BIN_ARCH="amd64"
[ "$ARCH" = "aarch64" ] && BIN_ARCH="arm64"

echo "==> Installing kubectl..."
curl -Lo /tmp/kubectl "https://dl.k8s.io/release/$(curl -Ls https://dl.k8s.io/release/stable.txt)/bin/linux/${BIN_ARCH}/kubectl"
sudo install -o root -g root -m 0755 /tmp/kubectl /usr/local/bin/kubectl

echo "==> Installing helm..."
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
sudo chmod a+rx /usr/local/bin/helm

echo "==> Installing kind..."
curl -Lo /tmp/kind "https://kind.sigs.k8s.io/dl/latest/kind-linux-${BIN_ARCH}"
sudo install -o root -g root -m 0755 /tmp/kind /usr/local/bin/kind

echo "==> Creating kind cluster 'cortex-demo'..."
kind create cluster --name cortex-demo --wait 60s

echo "==> Verifying cluster..."
kubectl cluster-info --context kind-cortex-demo

echo "==> Done."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
openapi: 3.0.0
info:
title: Demo Kubernetes
description: Demo entity for the Kubernetes agent integration
x-cortex-tag: demo-kubernetes
x-cortex-type: service
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Bundled copy of the Cortex k8s-agent helm chart for demo purposes.
# This copy is not kept up-to-date. Once the chart is published to a
# public helm repo, this bundle will be replaced with a helm repo reference.
apiVersion: v2
name: cortex-k8s-agent
description: A Helm chart for deploying Cortex K8s agent in your cluster
type: application
version: 0.1.0
appVersion: 1.16.0
28 changes: 28 additions & 0 deletions cortexapps_cli/solutions/kubernetes-agent/helm-chart/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Cortex k8s Helm Chart

## Requirements
* [Helm](https://helm.sh/docs/intro/install/)
* A token for our package registry

## Process
1. Generate a new Cortex API Key on the [API Keys Settings tab](https://app.getcortexapp.com/admin/settings/api-keys) in Cortex.
- This will be used for the Cortex Kubernetes agent to communicate and push service information to Cortex backend without exposing your public API Key.
2. Inside your Kubernetes cluster, run the following command to generate a Kubernetes secret for the Cortex API Key.
`kubectl create secret generic cortex-key --from-literal api-key=YOUR_API_KEY`
3. Run `kubectl create secret docker-registry cortex-docker-registry-secret --docker-server=ghcr.io --docker-username=$GITHUB_USERNAME --docker-password=$GITHUB_PASSWORD --docker-email=<doesn't matter>`
4. Download the helm chart and inside the repository run the following command to install the agent in your cluster.
`helm install YOUR_SELECTED_CHART_NAME .`

## Customization
The helm chart make installation quick and simple, but if you want to customize any of the installation features for the Cortex agent you can do so by changing the following information in the `values.yaml` of the helm chart.
### Service Account
To authenticate the Cortex agent in your cluster and grant it access to service information, the agent needs its own service account. The helm chart by default creates a Service Account `cortex-service-account`, but you can customize the `name` and `namespace` of this Service Account. If you already have a Service Account that you want the Cortex agent to use, set `create: false` under `serviceAccount` and enter the `name` and `namespace` of the Service Account you wish to use.
### Service
The service type and port can be customized as well. For security, the agent uses a default `ClusterIP` service type that only allows the service to be accessed from within the cluster.
### Resources
By default, no resources are specified. While the Cortex Kubernetes agent is designed to be lightweight and minimize resource utilization, you have the option to add custom CPU limits and requests.
### Base URL
The Base URL defaults to that for the hosted version of Cortex. If you are using the on-prem version of Cortex, you should change the `app/baseUrl` value to the correct URL for your on-prem Cortex.

# Usage
After installation, usage is very simple as no additional steps are required. The next time you go to create a new service in your Service Directory Homepage, you should see all of your Kubernetes services already added, ready for you to use in Cortex. If you do not want to import all of your Kubernetes discovered services, you can simply remove the ones you do not want to add. Removed services will still show up in the Kubernetes tab of Discovered Services if you want to go back and add them later.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "helm-chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "helm-chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "helm-chart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "helm-chart.labels" -}}
helm.sh/chart: {{ include "helm-chart.chart" . }}
{{ include "helm-chart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "helm-chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "helm-chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "helm-chart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "helm-chart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Create the namespace of the service account to use
*/}}
{{- define "helm-chart.serviceAccountNamespace" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "helm-chart.fullname" .) .Values.serviceAccount.namespace }}
{{- else }}
{{- default "default" .Values.serviceAccount.namespace }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{{- if .Values.clusterRole.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "helm-chart.serviceAccountName" . }}
rules:
- apiGroups: ["apps", "argoproj.io", "batch"]
resources: ["deployments", "services", "pods", "replicationcontrollers", "statefulsets", "rollouts", "cronjobs"]
verbs: ["get", "watch", "list"]
{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{{- if .Values.clusterRoleBinding.create -}}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "helm-chart.serviceAccountName" . }}
subjects:
- kind: ServiceAccount
name: {{ include "helm-chart.serviceAccountName" . }}
namespace: {{ include "helm-chart.serviceAccountNamespace" . }}
roleRef:
kind: ClusterRole
name: {{ include "helm-chart.serviceAccountName" . }}
apiGroup: rbac.authorization.k8s.io
{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ template "helm-chart.fullname" . }}-configmap
data:
SPRING_PROFILES_ACTIVE: prod
BASE_URL: {{ required "Base URL must be defined." .Values.app.baseUrl }}
{{ if .Values.app.clusterName }}CORTEX_CLUSTER: {{ .Values.app.clusterName }}{{ end }}
{{ if .Values.app.namespace }}SELECTED_NAMESPACE: {{ .Values.app.namespace }}{{ end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "helm-chart.fullname" . }}
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
spec:
replicas: 1
selector:
matchLabels:
{{- include "helm-chart.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "helm-chart.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.image.secrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "helm-chart.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ required "Image repository must be defined" .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http-api
containerPort: 80
protocol: TCP
readinessProbe:
initialDelaySeconds: 30
periodSeconds: 5
httpGet:
path: /actuator/health
port: 8080
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
- name: CORTEX_API_KEY
valueFrom:
secretKeyRef:
name: {{ required "A secret containing api-key=CORTEX_API_KEY is required" .Values.app.keySecret }}
key: api-key
envFrom:
- configMapRef:
name: {{ template "helm-chart.fullname" . }}-configmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "helm-chart.fullname" . }}
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: http-api
protocol: TCP
name: http-{{- include "helm-chart.fullname" . }}
selector:
{{- include "helm-chart.selectorLabels" . | nindent 4 }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "helm-chart.serviceAccountName" . }}
namespace: {{ include "helm-chart.serviceAccountNamespace" . }}
labels:
{{- include "helm-chart.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
image:
repository: ghcr.io/cortexapps/k8s-agent/k8s-agent
pullPolicy: IfNotPresent
tag: ""
secrets:
- name: cortex-docker-registry-secret

imagePullSecrets: []
nameOverride: ""
fullnameOverride: cortex-k8s-agent

serviceAccount:
create: true
annotations: {}
name: cortex-service-account
namespace: default

clusterRole:
create: true

clusterRoleBinding:
create: true

service:
type: ClusterIP
port: 80

resources: {}

app:
baseUrl: https://api.getcortexapp.com
keySecret: cortex-key
clusterName: cortex-demo
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: demo-cronjob
labels:
app: demo-k8s-label
annotations:
cortex.io/tag: demo-kubernetes
spec:
schedule: "*/10 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox:latest
command:
- /bin/sh
- -c
- echo "$(date '+%Y-%m-%d %H:%M:%S') - Hello from demo-kubernetes cronjob" >> /tmp/hello-world.txt
restartPolicy: OnFailure
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-deployment
labels:
app: demo-k8s-label
annotations:
cortex.io/tag: demo-kubernetes
spec:
replicas: 1
selector:
matchLabels:
app: demo-k8s
template:
metadata:
labels:
app: demo-k8s
spec:
containers:
- name: hello
image: nginx:alpine
ports:
- containerPort: 80
Loading