-
Notifications
You must be signed in to change notification settings - Fork 26
docs: add Helm install path to enterprise Analytics guide #524
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,6 +11,16 @@ | |||||||||
|
|
||||||||||
| This guide is for users who want to explore analytics on their OpenHands Enterprise conversations. | ||||||||||
|
|
||||||||||
| It covers both supported install paths: | ||||||||||
|
|
||||||||||
| - **Replicated (VM install)** -- if you followed the [Quick Start](/enterprise/quick-start) and | ||||||||||
| manage OpenHands through the Replicated Admin Console. | ||||||||||
| - **Helm (Kubernetes install)** -- if you deployed the `openhands` Helm chart into your own | ||||||||||
| Kubernetes cluster (see [Kubernetes Installation](/enterprise/k8s-install/index)). | ||||||||||
|
|
||||||||||
| Most of the workflow (creating a Laminar project, creating an API key, viewing traces) is | ||||||||||
| the same on both paths. The two install-specific steps are tabbed below. | ||||||||||
|
|
||||||||||
| ### Why Laminar? | ||||||||||
|
|
||||||||||
| [Laminar](https://laminar.sh/) is an open source observability platform for AI agents like OpenHands. | ||||||||||
|
|
@@ -29,35 +39,203 @@ | |||||||||
|
|
||||||||||
| ### Prerequisites | ||||||||||
|
|
||||||||||
| Before you begin, make sure you completed the [Quick Start guide](/enterprise/quick-start). | ||||||||||
| Before you begin, make sure you have completed the install for your path: | ||||||||||
|
|
||||||||||
| ## Enable Analytics | ||||||||||
| - **Replicated**: [Quick Start guide](/enterprise/quick-start) | ||||||||||
| - **Helm**: [Kubernetes Installation guide](/enterprise/k8s-install/index) | ||||||||||
|
|
||||||||||
| You should see an **Analytics Configuration** section on the application configuration page. | ||||||||||
| You will also need: | ||||||||||
|
|
||||||||||
| Check the **Enable Analytics** box to have the installer set up and configure Laminar for analytics. | ||||||||||
| - DNS records (and a TLS certificate covering the SAN) for `analytics.app.<your-base-domain>`. | ||||||||||
| On Replicated, this is included in the [Quick Start DNS table](/enterprise/quick-start#dns-and-tls-setup). | ||||||||||
| On Helm, you choose the hostname yourself in `site-values.yaml`. | ||||||||||
| - An ingress controller already running in the cluster (Replicated installs ship Traefik; Helm | ||||||||||
| installs typically use Traefik as well -- see the [chart README](https://github.com/OpenHands/OpenHands-Cloud/blob/main/charts/openhands/README.md)). | ||||||||||
|
|
||||||||||
|  | ||||||||||
| ## Enable Analytics | ||||||||||
|
|
||||||||||
| <Tabs> | ||||||||||
| <Tab title="Replicated (VM Install)"> | ||||||||||
| On the application configuration page in the Admin Console, find the | ||||||||||
| **Analytics Configuration** section. | ||||||||||
|
|
||||||||||
| Check the **Enable Analytics** box. The installer will set up Laminar and template the | ||||||||||
| required hostnames and Keycloak wiring for you. | ||||||||||
|
|
||||||||||
|  | ||||||||||
| </Tab> | ||||||||||
|
|
||||||||||
| <Tab title="Helm (Kubernetes Install)"> | ||||||||||
| Add the `laminar` block to your `site-values.yaml` and set the top-level `env.LMNR_*` | ||||||||||
| keys so the application sends traces to Laminar. | ||||||||||
|
|
||||||||||
| This guide assumes a single-cluster install where OpenHands runtimes and Laminar run | ||||||||||
| in the same Kubernetes cluster. Traces are sent to the in-cluster Laminar Service, | ||||||||||
| so you only need **one** new DNS record and TLS SAN -- the user-facing Laminar UI | ||||||||||
| hostname (e.g. `analytics.app.<your-base-domain>`). | ||||||||||
|
|
||||||||||
| Replace `example.com` with your base domain, and replace `traefik` with the name of | ||||||||||
| your ingress controller's IngressClass. | ||||||||||
|
|
||||||||||
| ```yaml | ||||||||||
| # site-values.yaml | ||||||||||
|
|
||||||||||
| env: | ||||||||||
| # The application sends traces to the in-cluster Laminar Service. No external | ||||||||||
| # hostname is required for ingestion. | ||||||||||
| LMNR_BASE_URL: "http://laminar-app-server-service" | ||||||||||
| LMNR_FORCE_HTTP: "true" | ||||||||||
| LMNR_HTTP_PORT: "8000" | ||||||||||
| # LMNR_PROJECT_API_KEY is set in a later step, after you create an ingest-only key | ||||||||||
| # in the Laminar UI. | ||||||||||
|
|
||||||||||
| laminar: | ||||||||||
| enabled: true | ||||||||||
| global: | ||||||||||
| # Sets provider-specific defaults; not auto-detected. Use "gcp" or "aws". | ||||||||||
| cloudProvider: "gcp" | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: Consider either expanding the comment to list all valid values (or link directly to the relevant section of the lmnr-helm config guide), or use a placeholder like
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some customers will be in AWS? We may want to specify to the user with a comment specify your cloud provider AWS or GCP. The cloud provider also impacts laminar storage. Customers might be using a different storage type than the default. Defaults are listed here in the laminar chart values https://github.com/lmnr-ai/lmnr-helm/blob/main/charts/laminar/values.yaml#L837. The GCP default pd-balanced was not available for us in the SaaS GKE cluster. For SaaS in GCP GKE, there's hyperdisk-balanced https://github.com/OpenHands/deploy/blob/main/openhands/envs/production/values.yaml#L183. I checked in the cluster and that was the storage class type available. |
||||||||||
| frontend: | ||||||||||
| ingress: | ||||||||||
| enabled: true | ||||||||||
| hostname: "analytics.app.example.com" # REQUIRED -- the Laminar UI hostname | ||||||||||
| className: "traefik" # your ingress controller's IngressClass | ||||||||||
| externalDns: | ||||||||||
| enabled: false # true if external-dns manages your DNS | ||||||||||
| tls: | ||||||||||
| enabled: true | ||||||||||
| clusterIssuer: "" # see TLS options below | ||||||||||
| secretName: "laminar-frontend-tls" | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The customer can reuse the openhands cert secret here instead |
||||||||||
| env: | ||||||||||
| # Must match laminar.frontend.ingress.hostname above. | ||||||||||
| nextauthUrl: "https://analytics.app.example.com" | ||||||||||
| nextPublicUrl: "https://analytics.app.example.com" | ||||||||||
| extraEnv: | ||||||||||
| # Wires the Laminar UI to your existing Keycloak realm so users can sign in | ||||||||||
| # with the same identity provider they use for OpenHands. | ||||||||||
| - name: AUTH_KEYCLOAK_ID | ||||||||||
| valueFrom: | ||||||||||
| secretKeyRef: | ||||||||||
| name: keycloak-realm | ||||||||||
| key: client-id | ||||||||||
| - name: AUTH_KEYCLOAK_SECRET | ||||||||||
| valueFrom: | ||||||||||
| secretKeyRef: | ||||||||||
| name: keycloak-realm | ||||||||||
| key: client-secret | ||||||||||
| - name: AUTH_KEYCLOAK_ISSUER | ||||||||||
| value: "https://auth.app.example.com/realms/allhands" | ||||||||||
|
Comment on lines
+115
to
+126
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The config references a <Note>
The `keycloak-realm` secret is created automatically by the OpenHands Helm chart during initial install. If you need to update Keycloak configuration, see the [chart documentation](https://github.com/OpenHands/OpenHands-Cloud/blob/main/charts/openhands/README.md).
</Note>
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The instructions above say "Replace A brief inline comment would resolve the ambiguity:
Suggested change
|
||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ### TLS options for the frontend ingress | ||||||||||
|
|
||||||||||
| The `laminar.frontend.ingress.tls` block above works with either pattern: | ||||||||||
|
|
||||||||||
| - **Pre-existing TLS secret** (recommended if your DNS and certs are managed externally): | ||||||||||
| leave `clusterIssuer: ""` and create the secret yourself. Concatenate the certificate | ||||||||||
| and CA bundle into a full chain first: | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| cat cert.pem ca-bundle.pem > fullchain.pem | ||||||||||
|
|
||||||||||
| kubectl create secret tls laminar-frontend-tls \ | ||||||||||
| -n openhands --cert=fullchain.pem --key=private-key.pem | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| - **cert-manager with Let's Encrypt**: set `clusterIssuer: "letsencrypt"` (or the name | ||||||||||
| of any other `ClusterIssuer` in your cluster). The hostname must be publicly | ||||||||||
| DNS-resolvable so Let's Encrypt can complete the HTTP-01 challenge. | ||||||||||
|
|
||||||||||
| ### Apply the change | ||||||||||
|
|
||||||||||
| ```bash | ||||||||||
| helm upgrade --install openhands \ | ||||||||||
| --namespace openhands \ | ||||||||||
| oci://ghcr.io/all-hands-ai/helm-charts/openhands \ | ||||||||||
| -f site-values.yaml | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The Consider adding a note that users should pin the version to match what they originally installed, e.g.:
Suggested change
Alternatively, a brief prose note like "If you want to stay on your current chart version, add |
||||||||||
| ``` | ||||||||||
|
|
||||||||||
| <Accordion title="Advanced: expose the app-server externally (multi-cluster only)"> | ||||||||||
| Skip this section unless OpenHands runtimes will be sending traces from **outside** | ||||||||||
| the cluster where Laminar runs. Almost all installs are single-cluster and should | ||||||||||
| use the in-cluster ingest configuration above. | ||||||||||
|
|
||||||||||
| For multi-cluster setups, expose the Laminar app-server through your ingress (or, on | ||||||||||
| AWS, an L4 Network Load Balancer) and point `LMNR_BASE_URL` at the external hostname. | ||||||||||
| This adds a second DNS record and TLS SAN (e.g. `laminar-api.app.<your-base-domain>`). | ||||||||||
|
|
||||||||||
| ```yaml | ||||||||||
| # site-values.yaml | ||||||||||
|
|
||||||||||
| env: | ||||||||||
| LMNR_BASE_URL: "https://laminar-api.app.example.com" | ||||||||||
| LMNR_FORCE_HTTP: "true" | ||||||||||
| # Omit LMNR_HTTP_PORT -- the port comes from the URL. | ||||||||||
|
|
||||||||||
| laminar: | ||||||||||
| appServer: | ||||||||||
| ingress: | ||||||||||
| hostname: "laminar-api.app.example.com" | ||||||||||
| className: "traefik" | ||||||||||
| externalDns: | ||||||||||
| enabled: false | ||||||||||
| tls: | ||||||||||
| enabled: true | ||||||||||
| clusterIssuer: "" | ||||||||||
| secretName: "laminar-app-server-tls" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| **AWS clusters** can swap `laminar.appServer.ingress` for an L4 Network Load Balancer: | ||||||||||
|
|
||||||||||
| ```yaml | ||||||||||
| laminar: | ||||||||||
| appServer: | ||||||||||
| loadBalancer: | ||||||||||
| enabled: true | ||||||||||
| hostname: "laminar-api.app.example.com" | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| See the [lmnr-helm configuration guide](https://github.com/lmnr-ai/lmnr-helm/blob/main/CONFIGURATION.md) | ||||||||||
| for additional DNS and TLS variants, including manual DNS and pre-existing ACM certificates. | ||||||||||
| </Accordion> | ||||||||||
| </Tab> | ||||||||||
| </Tabs> | ||||||||||
|
|
||||||||||
| ## Deploy | ||||||||||
|
|
||||||||||
| OpenHands will begin deploying. You can expect the deployment status to transition from | ||||||||||
| **Missing** to **Unavailable** to **Ready**. This typically takes 10-15 minutes. | ||||||||||
| <Tabs> | ||||||||||
| <Tab title="Replicated (VM Install)"> | ||||||||||
| OpenHands will begin deploying. You can expect the deployment status to transition from | ||||||||||
| **Missing** to **Unavailable** to **Ready**. This typically takes 10-15 minutes. | ||||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| Click **Details** next to the deployment status to monitor individual resources. Resources | ||||||||||
| shown in orange are still deploying -- wait until all resources are ready. | ||||||||||
|
|
||||||||||
|  | ||||||||||
|  | ||||||||||
| </Tab> | ||||||||||
|
|
||||||||||
| Click **Details** next to the deployment status to monitor individual resources. Resources | ||||||||||
| shown in orange are still deploying -- wait until all resources are ready. | ||||||||||
| <Tab title="Helm (Kubernetes Install)"> | ||||||||||
| Watch the Laminar pods come up in your cluster: | ||||||||||
|
|
||||||||||
|  | ||||||||||
| ```bash | ||||||||||
| kubectl get pods -n openhands -l app.kubernetes.io/instance=openhands -w | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| You should see pods for `laminar-frontend`, `laminar-app-server`, `laminar-clickhouse`, | ||||||||||
| `laminar-postgres`, `laminar-rabbitmq`, `laminar-redis`, and the Quickwit components. | ||||||||||
| Wait until all pods are `Running` and ready before continuing. | ||||||||||
| </Tab> | ||||||||||
| </Tabs> | ||||||||||
|
|
||||||||||
| ## Access Laminar UI | ||||||||||
|
|
||||||||||
| Once the deployment status shows **Ready**, navigate to `https://analytics.app.<your-base-domain>`. | ||||||||||
| Once the deployment is **Ready**, navigate to `https://analytics.app.<your-base-domain>` | ||||||||||
| (or the `laminar.frontend.ingress.hostname` you configured for the Helm install). | ||||||||||
|
|
||||||||||
| Click the **Continue with Keycloak** button: | ||||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| ## Create a Laminar project | ||||||||||
|
|
||||||||||
|
|
@@ -67,29 +245,69 @@ | |||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| ## Create an ingest only API Key | ||||||||||
| ## Create an ingest-only API Key | ||||||||||
|
|
||||||||||
| Important: Always use ingest API keys when deploying. | ||||||||||
| <Warning> | ||||||||||
| Always use **ingest-only** API keys for the OpenHands integration. Ingest-only keys can | ||||||||||
| only write traces -- they cannot be used to read data, so they are safe to embed in | ||||||||||
| configuration. | ||||||||||
| </Warning> | ||||||||||
|
|
||||||||||
| Create a key with ther right permissions. Ingest only keys are recommended as they only have write access to write traces. They cannot be used to read data. | ||||||||||
| Create a key with ingest-only permissions: | ||||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| ## Set Laminar Project API Key to enable automatic conversation traces | ||||||||||
| ## Wire the API key into the install | ||||||||||
|
|
||||||||||
| <Tabs> | ||||||||||
| <Tab title="Replicated (VM Install)"> | ||||||||||
| Paste the ingest-only key into the **Laminar Project API Key** field in the Admin Console | ||||||||||
| configuration: | ||||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| Click **Save config**, then deploy the change: | ||||||||||
|
|
||||||||||
|  | ||||||||||
|
|
||||||||||
| Wait for the deployment to complete. | ||||||||||
| </Tab> | ||||||||||
|
|
||||||||||
| <Tab title="Helm (Kubernetes Install)"> | ||||||||||
| Set the ingest-only key in your `site-values.yaml` under the top-level `env` block: | ||||||||||
|
|
||||||||||
| Set the ingest only key as the Laminar Project API Key in the Admin Console configuration: | ||||||||||
| ```yaml | ||||||||||
| # site-values.yaml | ||||||||||
|
|
||||||||||
|  | ||||||||||
| env: | ||||||||||
| LMNR_BASE_URL: "http://laminar-app-server-service" | ||||||||||
| LMNR_FORCE_HTTP: "true" | ||||||||||
| LMNR_HTTP_PORT: "8000" | ||||||||||
| LMNR_PROJECT_API_KEY: "<paste-your-ingest-only-key-here>" | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion: The main example shows the API key in plaintext YAML, which could lead users to commit secrets to version control. Consider showing the Secret-based approach as the primary example: env:
LMNR_BASE_URL: "https://laminar-api.app.example.com"
LMNR_FORCE_HTTP: "true"
LMNR_PROJECT_API_KEY:
valueFrom:
secretKeyRef:
name: laminar-api-key
key: api-keyThen show how to create the secret: kubectl create secret generic laminar-api-key \
-n openhands --from-literal=api-key='<your-ingest-only-key>' |
||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Click **Save config**. | ||||||||||
| <Tip> | ||||||||||
| For a production install, store the key in a Kubernetes Secret and reference it from | ||||||||||
| your values file or via `--set-string` at install time, rather than committing it to | ||||||||||
| source control. | ||||||||||
| </Tip> | ||||||||||
|
|
||||||||||
| ## Deploy Updated Configuration | ||||||||||
| Apply the change: | ||||||||||
|
|
||||||||||
| Deploy the config change after setting the Laminar Project API Key in the Admin Console. | ||||||||||
| ```bash | ||||||||||
| helm upgrade --install openhands \ | ||||||||||
| --namespace openhands \ | ||||||||||
| oci://ghcr.io/all-hands-ai/helm-charts/openhands \ | ||||||||||
| -f site-values.yaml | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
|  | ||||||||||
| Wait for the rollout to complete: | ||||||||||
|
|
||||||||||
| Wait for the deployment to complete. | ||||||||||
| ```bash | ||||||||||
| kubectl rollout status deploy/openhands -n openhands | ||||||||||
| ``` | ||||||||||
| </Tab> | ||||||||||
| </Tabs> | ||||||||||
|
|
||||||||||
| ## Start a conversation | ||||||||||
|
|
||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it is a one cluster setup then I think this is fine