Deploys the Elastic Cloud on Kubernetes (ECK) operator and an Elasticsearch +
Kibana stack onto a Kubernetes cluster, with persistent storage on Azure Disk
(the managed-csi StorageClass).
- CRDs define new resource types (
Elasticsearch,Kibana, …). - The ECK operator watches those resources and reconciles the cluster — it understands Elasticsearch-specific behaviour (shard allocation, cluster health, safe rolling upgrades) that plain Kubernetes does not.
- You declare an
Elasticsearch/KibanaCR; the operator does the rest.
| File | What |
|---|---|
crds.yaml, operator.yaml |
vendored ECK operator v3.4.0 (supports Elastic Stack 9.x) |
elastic-stack/elasticsearch.yaml |
3-node ES (1 master + 2 data), PVs on managed-csi |
elastic-stack/kibana.yaml |
Kibana, wired to the quickstart ES cluster |
install.sh |
one-shot installer |
kubectlpointed at your cluster (e.g. the k8s-azure-kubeadm cluster — see itsdocs/access.md).- A
managed-csiStorageClass (Azure Disk CSI). Verify:kubectl get storageclass # expect managed-csi (default) vm.max_map_count≥ 262144 on the nodes (ES uses mmap). The k8s-azure-kubeadmcommonrole sets this cluster-wide, so the ES pods need no privileged init container and run withnode.store.allow_mmap: true. On a cluster that doesn't set it, either raise it on the nodes, or setallow_mmap: false.
./install.shOr step by step:
# 1) ECK operator — a cluster-wide prerequisite, install once
kubectl apply --server-side -f crds.yaml # CRDs (server-side apply: big file)
kubectl apply --server-side -f operator.yaml # operator in namespace elastic-system
kubectl -n elastic-system rollout status statefulset/elastic-operator
# 2) the Elasticsearch + Kibana stack — via kustomize
kubectl apply -k .The operator/CRDs are intentionally not in
kustomization.yaml(CRDs must exist before theElasticsearch/KibanaCRs apply).kubectl apply -k .manages just the stack — re-run it any time to update ES/Kibana.
Plain kubectl apply stores the whole object in a
kubectl.kubernetes.io/last-applied-configuration annotation. Kubernetes caps
annotations at 262,144 bytes (256 KB) — and ECK's crds.yaml is ~742 KB:
wc -c < crds.yaml # 759872 -> ~742 KB, ~3x over the 256 KB limitSo kubectl apply -f crds.yaml fails with metadata.annotations: Too long. There
are two ways around it:
| Command | Big CRDs? | Idempotent? | Upgrades? |
|---|---|---|---|
kubectl apply -f (client-side) |
❌ annotation too long | — | — |
kubectl create -f |
✅ (writes no annotation) | ❌ re-run → AlreadyExists |
needs kubectl replace |
kubectl apply --server-side -f |
✅ (ownership tracked server-side, no client annotation) | ✅ | ✅ |
ECK's classic docs say use kubectl create for exactly this reason. This repo uses
server-side apply instead: it sidesteps the same annotation limit and stays
idempotent and upgrade-safe — important because install.sh may be re-run.
Instead of install.sh, you can have Argo CD reconcile this repo from git.
The Application manifests live in argocd/:
eck-operator-application.yaml— installs the ECK operator (Helm chart). Optional — skip it if you prefer the manualkubectl apply -f crds.yaml -f operator.yaml.eck-stack-application.yaml— the Elasticsearch + Kibana stack (path.; switch tooverlays/localfor non-Azure).
Argo CD itself is not installed here — that's the cluster's job (the k8s-azure-kubeadm platform installs it). Once Argo CD is on the cluster, register the apps:
kubectl apply -f argocd/Then a git push to this repo auto-syncs the cluster (prune + selfHeal). Edit
repoURL in those files if you fork this repo.
kubectl get elasticsearch,kibana # HEALTH should reach green
kubectl get pods # quickstart-es-*, quickstart-kb-*
kubectl get pvc # bound to managed-csi (Azure disks)kubectl get secret quickstart-es-elastic-user -o go-template='{{.data.elastic | base64decode}}'; echo
<CP_PUBLIC_IP>and the SSH key belong to the separate cluster repoelmi70/K8S-AZURE-KUBEADM— this repo only deploys the Elastic stack onto that cluster. Get the IP with:az network public-ip show -g k8s-kubeadm-rg -n k8s-cp-0-pip --query ipAddress -o tsvFull SSH-key/kubectl setup is in that repo's
docs/access.md(the key lands at~/.ssh/k8s-cp).
Kibana is HTTPS-only. Tunnel laptop:5601 → VM:localhost:5601, and have the VM
port-forward to the Kibana Service (one command). Replace <CP_PUBLIC_IP> with the
control-plane public IP:
ssh -i ~/.ssh/k8s-cp -L 5601:localhost:5601 azureuser@<CP_PUBLIC_IP> \
kubectl port-forward service/quickstart-kb-http 5601:5601Then open https://localhost:5601 — user elastic, password from above.
ssh -i ~/.ssh/k8s-cp -L 9200:localhost:9200 azureuser@<CP_PUBLIC_IP> \
kubectl port-forward service/quickstart-es-http 9200:9200
# in another shell:
PASSWORD=$(kubectl get secret quickstart-es-elastic-user -o go-template='{{.data.elastic | base64decode}}')
curl -k -u "elastic:$PASSWORD" https://localhost:9200Both ES nodeSets use volumeClaimTemplates with storageClassName: managed-csi,
so the Azure Disk CSI driver provisions a managed disk per ES pod and attaches
it to that pod's node:
- master node → 10Gi
- each data node → 50Gi
On a non-Azure cluster (kind, minikube, a bare kubeadm box) there's no managed-csi.
Use the overlays/local kustomize overlay, which swaps storage to the
local-path provisioner (host-local volumes) and disables mmap (local nodes
usually don't raise vm.max_map_count).
# 1) install the local-path provisioner (creates the `local-path` StorageClass)
kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.36/deploy/local-path-storage.yaml
# 2) install the ECK operator (same as Azure)
kubectl apply --server-side -f crds.yaml -f operator.yaml
# 3) deploy the stack with the local overlay (local-path storage, mmap off)
kubectl apply -k overlays/localThe base (kubectl apply -k .) stays Azure/managed-csi; only overlays/local
changes storage — so the same repo works on both.
kubectl delete -f elastic-stack/kibana.yaml -f elastic-stack/elasticsearch.yaml
kubectl delete -f operator.yaml -f crds.yaml
# PVCs (and their Azure disks) are kept by default — delete to reclaim:
kubectl delete pvc -l elasticsearch.k8s.elastic.co/cluster-name=quickstart