Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions examples/operations/helm/Chart.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
dependencies:
- name: config
repository: ""
version: 0.1.0
- name: services
repository: ""
version: 0.1.0
digest: sha256:7550b9a2ad831bd383e6bf22c51013efd466f8d287df676433766fd59e9aac29
generated: "2025-04-10T10:54:29.929533+01:00"
11 changes: 11 additions & 0 deletions examples/operations/helm/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: v2
name: vespa
version: 0.1.0
dependencies:
- name: config
alias: config
version: 0.1.0
- name: services
alias: services
version: 0.1.0
condition: services.enabled
170 changes: 170 additions & 0 deletions examples/operations/helm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<!-- Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -->

<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://assets.vespa.ai/logos/Vespa-logo-green-RGB.svg">
<source media="(prefers-color-scheme: light)" srcset="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg">
<img alt="#Vespa" width="200" src="https://assets.vespa.ai/logos/Vespa-logo-dark-RGB.svg" style="margin-bottom: 25px;">
</picture>

# Multinode-HA using Helm
This guide uses the multinode-HA configuration and principles and deploys a Vespa application using Kubernetes and Helm.

## Overview
This deployment is designed for high availability and uses the Helm chart consisting of two primary modules:
1. `config` - contains and deploys **vespa-configserver**, which must be running successfully before starting other components.
2. `services` - starts **admin**, **content**, **feed**, and **query** clusters, depending on a successful `configserver` startup.

A key mechanism ensures the correct start order of the modules: `initContainers` in `services` waits for the `configserver` to become ready by repeatedly checking its health. Only after the `configserver` successfully initializes, the `services` module will proceed to start. Here’s the command used in the `initContainers`:

```bash
until curl -f http://vespa-configserver-0.vespa-internal.vespa.svc.cluster.local:19071/state/v1/health; do
echo "Waiting for Vespa ConfigServer to be ready in namespace $CONFIGSERVER_NAMESPACE...";
sleep 5;
done
```

---

## Prerequisites
Make sure the following tools are installed and configured:
* [Helm](https://helm.sh/docs/intro/install/)
* A Kubernetes cluster - either local or hosted (e.g., Azure AKS, AWS EKS, etc.)

---

## Installation
Clone the repository:
```bash
git clone --depth 1 https://github.com/vespa-engine/sample-apps.git
cd sample-apps/examples/operations/multinode-HA/helm
```

Prepare your `values.yaml` with the desired configuration. Here's an example:
```yaml
config:
serverReplicas: 3
services:
content:
replicas: 2
storage: 25Gi
```

Deploy Vespa using Helm:
```bash
helm dependency update helm
helm upgrade --install vespa . -n vespa --create-namespace -f values.yaml
```

This will create the namespace `vespa` and deploy all components of the application.

---

## Deploy the application package

```
kubectl port-forward -n vespa pod/vespa-configserver-0 19071
```

```
(cd conf && zip -r - .) | \
curl --header Content-Type:application/zip \
--data-binary @- \
http://localhost:19071/application/v2/tenant/default/prepareandactivate
```

---

## Module Details

### Config Module
The `config` module contains the **vespa-configserver**, which is essential for Vespa's operation. This module deploys a StatefulSet with `serverReplicas` to ensure high availability.

#### ConfigServer Health Check
The `configserver` health is verified by an HTTP curl to its `/state/v1/health` endpoint. The `services` module will not start until all `configserver` replicas are running and reachable.

Here’s an example of the expected `configserver` health response:
```json
{
"time" : 1678268549957,
"status" : {
"code" : "up"
},
"metrics" : {
"snapshot" : {
"from" : 1.678268489718E9,
"to" : 1.678268549718E9
}
}
}
```

### Services Module
The `services` module contains the following components:
- **Admin**: Handles Vespa cluster administration.
- **Feed**: Handles document feeding.
- **Query**: Handles document queries.
- **Content**: Stores indexed data.

This module is configured to depend on the `config` module startup. The `initContainers` logic ensures that no pods in the `services` module are started until the `configserver` reaches a stable, healthy state.

Below is the typical `initContainers` logic defined for `services`:
```bash
until curl -f http://vespa-configserver-0.vespa-internal.vespa.svc.cluster.local:19071/state/v1/health; do
echo "Waiting for Vespa ConfigServer to be ready in namespace $CONFIGSERVER_NAMESPACE...";
sleep 5;
done
```

---

## Verification
Once the installation completes, you can test the Vespa application by:
1. Feeding a document:
```bash
curl -X POST http://vespa-query-container-0.vespa.svc.cluster.local/document/v1/my-space/my-doc \
-d '{"id": "id:my-space:my-doc::1", "fields": {"field1": "value1"}}'
```

2. Querying documents:
```bash
curl "http://vespa-query-container-0.vespa.svc.cluster.local/search/?query=my-query"
```

or

```
kubectl -n vespa port-forward svc/vespa-query 8080
```
```
curl --data-urlencode 'yql=select * from sources * where true' \
http://localhost:8080/search/
```

---

## Customization and Scaling
Values such as `config.serverReplicas`, `services.content.replicas`, and `services.content.storage` can be adjusted in `values.yaml` to match your requirements for scaling and resource configuration. For example:
```yaml
config:
serverReplicas: 5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
serverReplicas: 5
serverReplicas: 3

Let's keep three as the default number of config servers recommended across all documentation. More than that adds extra operational work, with minimal advantages.

services:
content:
replicas: 4
storage: 50Gi
```

Refer to the official [Vespa documentation](https://docs.vespa.ai/en/) for advanced deployment details and customization options.

---

## Troubleshooting
Check Helm release status to confirm all components deployed successfully:
```bash
helm status vespa -n vespa
```

If pods are stuck, ensure that:
1. The `configserver` is running and reachable.
2. Kubernetes networking allows communication between the pods.

For further troubleshooting details, refer to the [Vespa troubleshooting guide](https://docs.vespa.ai/en/operations.html).
3 changes: 3 additions & 0 deletions examples/operations/helm/charts/config/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
apiVersion: v2
name: config
version: 0.1.0
15 changes: 15 additions & 0 deletions examples/operations/helm/charts/config/templates/configmap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: vespa-config
data:
VESPA_CONFIGSERVERS: >-
{{- $domain := printf "%s.%s.svc.cluster.local" .Values.headlessName .Release.Namespace }}
{{- $replicas := int .Values.serverReplicas }}
{{- $serverList := list }}
{{- range $i := until $replicas }}
{{- $serverList = append $serverList (printf "vespa-configserver-%d.%s" $i $domain) }}
{{- end }}
{{ join "," $serverList }}
VESPA_CONFIGSERVER_JVMARGS: "{{ .Values.serverJvmArgs }}"
VESPA_CONFIGPROXY_JVMARGS: "{{ .Values.proxyJvmArgs }}"
84 changes: 84 additions & 0 deletions examples/operations/helm/charts/config/templates/configserver.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vespa-configserver
spec:
# Use three servers for proper ZooKeeper quorum:
replicas: {{ .Values.serverReplicas }}
selector:
matchLabels:
app: vespa-configserver
name: {{ .Values.headlessName }}
serviceName: {{ .Values.headlessName }}
template:
metadata:
labels:
app: vespa-configserver
name: {{ .Values.headlessName }}
spec:
initContainers:
- name: chown-var
securityContext:
runAsUser: 0
image: busybox
command: ["sh", "-c", "chown -R 1000 /opt/vespa/var"]
volumeMounts:
- name: vespa-var
mountPath: /opt/vespa/var
- name: chown-logs
securityContext:
runAsUser: 0
image: busybox
command: ["sh", "-c", "chown -R 1000 /opt/vespa/logs"]
volumeMounts:
- name: vespa-logs
mountPath: /opt/vespa/logs
containers:
- name: vespa-configserver
image: vespaengine/vespa
args: ["configserver,services"]
imagePullPolicy: Always
securityContext:
runAsUser: 1000
volumeMounts:
- name: vespa-var
mountPath: /opt/vespa/var
- name: vespa-logs
mountPath: /opt/vespa/logs
- name: vespa-workspace
mountPath: /workspace
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this mount point used for? I can't find any references to it anywhere else in the chart.

envFrom:
- configMapRef:
name: vespa-config
# Note that the below are minimum resources for demo use.
# Use 4G or more for real use cases
resources:
requests:
memory: "1.5G"
limits:
memory: "1.5G"
Comment on lines +59 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make memory configurable from values for easier override?

volumeClaimTemplates:
# The below are tiny volumes for demo purposes.
- metadata:
name: vespa-var
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi
- metadata:
name: vespa-logs
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 5Gi
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make volume claim sizes configurable for easier override?

- metadata:
name: vespa-workspace
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
12 changes: 12 additions & 0 deletions examples/operations/helm/charts/config/templates/headless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

apiVersion: v1
kind: Service
metadata:
name: {{ .Values.headlessName }}
labels:
name: {{ .Values.headlessName }}
spec:
selector:
name: {{ .Values.headlessName }}
clusterIP: None
4 changes: 4 additions & 0 deletions examples/operations/helm/charts/config/values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
headlessName: "vespa-internal"
serverReplicas: 3
serverJvmArgs: "-Xms32M -Xmx128M"
proxyJvmArgs: "-Xms32M -Xmx32M"
3 changes: 3 additions & 0 deletions examples/operations/helm/charts/services/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
apiVersion: v2
name: services
version: 0.1.0
50 changes: 50 additions & 0 deletions examples/operations/helm/charts/services/templates/admin.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: vespa-admin
spec:
replicas: 1
selector:
matchLabels:
app: vespa-admin
name: vespa-internal
serviceName: vespa-internal
template:
metadata:
labels:
app: vespa-admin
name: vespa-internal
spec:
initContainers:
- name: wait-for-configserver
image: curlimages/curl
env:
- name: CONFIGSERVER_NAMESPACE
value: {{ .Release.Namespace }}
- name: CONFIGSERVER_SERVICE
value: vespa-configserver-0.vespa-internal
command:
- /bin/sh
- -c
- |
until curl -f http://vespa-configserver-0.vespa-internal.vespa.svc.cluster.local:19071/state/v1/health; do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to get vespa-internal and vespa (namespace) values from the templating functionality to support other values for this without changing the chart?

echo "Waiting for Vespa ConfigServer to be ready in namespace $CONFIGSERVER_NAMESPACE...";
sleep 5;
done
containers:
- name: vespa-admin
image: vespaengine/vespa
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to support specifying the Vespa version, defaulting to latest.

args: ["services"]
imagePullPolicy: Always
envFrom:
- configMapRef:
name: vespa-config
securityContext:
runAsUser: 1000
resources:
requests:
memory: "1G"
limits:
memory: "1G"
Loading
Loading