# On-Premises Helm

Пошагово здесь: https://elma365.com/ru/help/install-kubernetes.html

# Pods

# Удалить evicted pods

```bash
kubectl get po -A --all-namespaces -o json | jq  '.items[] | select(.status.reason!=null) | select(.status.reason | contains("Evicted")) | "kubectl delete po \(.metadata.name) -n \(.metadata.namespace)"' | xargs -n 1 bash -c
```

<p class="callout warning">Предварительно должен быть установлен jq</p>

# Полезное

# Перезапуск всех сервисов

```bash
kubectl -n default rollout restart deploy
kubectl -n default rollout restart ds
```

Если namespace отличен от по умолчанию, заменить default на свой

Более интересный вариант:

```bash
#!/usr/bin/env bash

function clearCompleted() {
    kubectl delete job $(kubectl get job -o=jsonpath='{.items[?(@.status.succeeded==1)].metadata.name}') > /dev/null 2>&1
}


kubectl scale deploy --replicas=0 --all

while [[ $(kubectl get pods -n default | grep -c "") -ne 0 ]]
do
  clearCompleted
  echo "Waiting for all pods are Terminating."
  sleep 15
done

kubectl scale deploy --replicas=1 --all
kubectl scale --replicas=4 deployment/worker

while true
do
  clearCompleted
  echo "Waiting for all pods are Ready."
  kubectl wait pods --all -n default --for condition=Ready --timeout=60s > /dev/null 2>&1 && break
  sleep 15
done

echo "All done."
```

# Установка порта отличного от 80

```bash
tar -xzf elma365.tar.gz elma365

sed -i -e 's/: 80/: 88/g' elma365/charts/front/templates/ingress.yaml
```

# Проверка статуса узлов кластера

```bash
etcdctl --cluster=true endpoint health
```

# Переход на Cilium Gateway

<p class="callout danger">Не решен вопрос с CORS</p>

Перед переходом на Cilium Gateway необходимо выполнить шаги, описанные [тут](https://kb.randmgroup.ru/books/k8s/page/perexod-s-ingress-na-cilium-gateway "Переход с Ingress на Cilium Gateway"). Здесь мы рассмотрим вариант для single node редакция Enterprise версия **2026.4.42**. Для редакции Hub все намного сложнее и будет описано позже.

Из справки [тут](https://elma365.com/ru/help/platform/cilium-gateway.html#create-gateway) создаем gateway:

```yaml
kubectl apply -n kube-system -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
   name: cilium-gateway
spec:
   gatewayClassName: cilium
   listeners:
   - name: http
     port: 80
     protocol: HTTP
     allowedRoutes:
       namespaces:
        from: All
   - name: https
     port: 443
     protocol: HTTPS
     tls:
       mode: Terminate
       certificateRefs:
       - name: elma365-tls
     allowedRoutes:
       namespaces:
         from: All
EOF
```

<p class="callout warning">Нам не нужен 443, т.к. у нас используется reverse proxy, который выполняет все нужные активности в части HTTPS</p>

Поэтому в упрощенному случае у нас будет:

```yaml
kubectl apply -n kube-system -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
   name: cilium-gateway
spec:
   gatewayClassName: cilium
   listeners:
   - name: http
     port: 80
     protocol: HTTP
     allowedRoutes:
       namespaces:
        from: All
EOF
```

Должны получить:

```shell
gateway.gateway.networking.k8s.io/cilium-gateway created
```

Смотрим что на самом деле:

```bash
kubectl get gateway -A
```

Видим, что появился gateway:

```shell
NAMESPACE     NAME             CLASS    ADDRESS   PROGRAMMED   AGE
kube-system   cilium-gateway   cilium             True         71s
```

Смотрим что у нас с сервисами:

```bash
kubectl get svc -n kube-system
```

Видим что появился LoadBalancer, но он еще не настроен (а еще какой то новый сервис hubble-peer):

```
NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
cilium-gateway-cilium-gateway       LoadBalancer   10.152.183.225   <pending>     80:30514/TCP                 4m13s
ck-storage-rawfile-csi-controller   ClusterIP      None             <none>        <none>                       189d
ck-storage-rawfile-csi-node         ClusterIP      10.152.183.56    <none>        9100/TCP                     189d
coredns                             ClusterIP      10.152.183.238   <none>        53/UDP,53/TCP                189d
hubble-peer                         ClusterIP      10.152.183.166   <none>        443/TCP                      189d
metrics-server                      ClusterIP      10.152.183.180   <none>        443/TCP                      189d
```

<p class="callout warning">Возможно можно как то настроить NodePort для ситуации single node, но эту гипотезу мы тут не проверяем.</p>

Так же load-balancer отключен по k8s status - его нужно включить:

```bash
k8s enable load-balancer
```

Должны получить:

```
Enabling load-balancer on the cluster. This may take a few seconds, please wait.
load-balancer enabled.

# k8s status

cluster status:           ready
control plane nodes:      192.168.56.83:6400 (voter)
high availability:        no
datastore:                etcd
network:                  enabled
dns:                      enabled at 10.152.183.238
ingress:                  disabled
load-balancer:            enabled, L2 mode
local-storage:            enabled at /var/snap/k8s/common/rawfile-storage
gateway                   enabled

# kubectl get pod,svc -n metallb-system

NAME                                      READY   STATUS    RESTARTS   AGE
pod/metallb-controller-676c8f595c-5chlt   1/1     Running   0          2m6s
pod/metallb-speaker-t2x96                 1/1     Running   0          2m6s

NAME                              TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/metallb-webhook-service   ClusterIP   10.152.183.39   <none>        443/TCP   2m7s
```

Неоходимо для нашего LoadBalancer назначить пул IP адресов (в нашем случае у нас single node и поэтому укажем только один):

<p class="callout warning">Использовать IP нашего узла НЕЛЬЗЯ. Необходимо указать свободный адрес в вашей сети. (а может и можно)!</p>

```bash
k8s set load-balancer.cidrs=192.168.56.73-192.168.56.73
```

Получилось:

```bash
Configuration updated.

# kubectl get svc -n kube-system

NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)         AGE
cilium-gateway-cilium-gateway       LoadBalancer   10.152.183.225   192.168.56.73   80:30514/TCP    86m
ck-storage-rawfile-csi-controller   ClusterIP      None             <none>          <none>          189d
ck-storage-rawfile-csi-node         ClusterIP      10.152.183.56    <none>          9100/TCP        189d
coredns                             ClusterIP      10.152.183.238   <none>          53/UDP,53/TCP   189d
hubble-peer                         ClusterIP      10.152.183.166   <none>          443/TCP         189d
metrics-server                      ClusterIP      10.152.183.180   <none>          443/TCP         189d
```

Теперь переходим непосредственно к настройке ELMA365. Правим наш values-elma365.yaml. Отключаем Ingress и [включаем](https://elma365.com/ru/help/platform/cilium-gateway.html#gateway-api-support) Gateway API:

```
global:

  gatewayAPI:
    enabled: true
    parentRefs:
      - name: cilium-gateway
        namespace: kube-system

  ingress:
    enabled: false
```

Устанавливаем (обновляем) чарт ELMA365 как это мы делаем стандартно (у каждого свой подход), но суть - применить наши изменения.

<p class="callout danger">И тут начались танцы с бубном....</p>

```bash
helm upgrade --install elma365 ./elma365 -f values-elma365.yaml --timeout=30m --debug --wait
```

Получили (причем тут istio вообще?):

```shell
level=DEBUG msg="number of dependencies in the chart" chart=messenger-vkteamsbot dependencies=0
level=DEBUG msg="number of dependencies in the chart" chart=messenger-whatsapp dependencies=0
level=DEBUG msg="number of dependencies in the chart" chart=postman dependencies=0
Error: unable to build kubernetes objects from release manifest: [resource mapping not found for name: "balancer-auth" namespace: "" from "": no matches for kind "AuthorizationPolicy" in version "security.istio.io/v1"
ensure CRDs are installed first, resource mapping not found for name: "main-body-size-limit" namespace: "istio-system" from "": no matches for kind "EnvoyFilter" in version "networking.istio.io/v1alpha3"
ensure CRDs are installed first]
```

Изучив ~/elma365/charts/balancer/values.yaml было обнаружено интересное значение workloadSelector, про которое ничего не сказано в оффициальной справке:

```
global:
  gatewayAPI:
    enabled: false
    parentRefs:
      - name: main-gateway
        namespace: istio-system
    workloadSelector:
      gateway.istio.io/managed: istio.io-gateway-controller
    annotations: {}
    hosts: []
```

Заменили значение на

Смотрим ~/elma365/charts/balancer/templates/httproute.yaml а там захаркодено если включен gatewayAPI:

```
---
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: {{ template "balancer.name" . }}-auth
  labels:
    {{- include "balancer.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      app: {{ template "balancer.name" . }}
  action: DENY
  rules:
    - to:
        - operation:
            ports: ["{{ $servicePort }}"]
            paths: ["/balancer*"]
      when:
        - key: request.headers[authorization]
          values: ["*"]
```

Пробуем удалить (далее сделаем патч к дистрибутиву и пробуем установить снова ELMA365 - эта проблема ушла но осталась вторая:

```shell
level=DEBUG msg="number of dependencies in the chart" chart=textextractor dependencies=0
level=DEBUG msg="number of dependencies in the chart" chart=widget dependencies=0
Error: unable to build kubernetes objects from release manifest: resource mapping not found for name: "main-body-size-limit" namespace: "istio-system" from "": no matches for kind "EnvoyFilter" in version "networking.istio.io/v1alpha3"
ensure CRDs are installed first
```

Пробуем добавить отключение envoyFilter в нашей конфигурации, ведь мы никакой Envoy не устанавливали:

```
  gatewayAPI:
    enabled: true
    # annotations: {}
    parentRefs:
      - name: cilium-gateway
        namespace: kube-system
    # timeoutSeconds: "300s"
    # retries:
    #   attempts: 3
    envoyFilter:
      enabled: false
    #   namespace: "istio-system"
```

<p class="callout success">Установка пошла дальше! Ура! Ждем результат...</p>

Все поднялось без ошибок. Смотрим какие у нас появились ресурсы HTTPRoute:

```bash
kubectl get httproute -A
```

Получили что то похожее:

```shell
NAMESPACE   NAME             HOSTNAMES         AGE
default     balancer         ["d.rmg365.ru"]   9m33s
default     fileprotection   ["d.rmg365.ru"]   9m33s
default     front            ["d.rmg365.ru"]   9m33s
default     hydra-adaptor    ["d.rmg365.ru"]   9m33s
default     main             ["d.rmg365.ru"]   9m33s
default     main-publicapi   ["d.rmg365.ru"]   9m33s
default     notifier         ["d.rmg365.ru"]   9m33s
default     notigate         ["d.rmg365.ru"]   9m33s
default     vahter           ["d.rmg365.ru"]   9m33s
default     web-forms        ["d.rmg365.ru"]   9m33s
```

Перенастроили reverse proxy на наш новый адрес, что мы указали ранее для LB и вуаля:

[![image.png](https://kb.randmgroup.ru/uploads/images/gallery/2026-08/scaled-1680-/oZjlD7GJKBV2Y3Jw-image.png)](https://kb.randmgroup.ru/uploads/images/gallery/2026-08/oZjlD7GJKBV2Y3Jw-image.png)

Давайте попробуем все же обмануть LB и указать ему адрес хоста, чтобы не плодить у себя айпишники:

```
k8s set load-balancer.cidrs=192.168.56.83-192.168.56.83
```

Вроде не ругнулось даже:

```
Configuration updated.

# kubectl get svc -n kube-system
NAME                                TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)         AGE
cilium-gateway-cilium-gateway       LoadBalancer   10.152.183.225   192.168.56.83   80:30514/TCP    158m
ck-storage-rawfile-csi-controller   ClusterIP      None             <none>          <none>          190d
ck-storage-rawfile-csi-node         ClusterIP      10.152.183.56    <none>          9100/TCP        190d
coredns                             ClusterIP      10.152.183.238   <none>          53/UDP,53/TCP   190d
hubble-peer                         ClusterIP      10.152.183.166   <none>          443/TCP         190d
metrics-server                      ClusterIP      10.152.183.180   <none>          443/TCP         190d
```

<p class="callout success">Эксперимент прошел удачно и мы вернули настройки reverse proxy как было в случае Ingress.</p>

Ну вот собственно и патч для 2026.4 (актуально для 2026.4.42, покка не исплавили) - balancer-httproute.patch.2026.4

```diff
--- elma365/charts/balancer/templates/httproute.yaml	2026-07-31 09:06:50.000000000 +0300
+++ ./httproute.yaml	2026-08-02 14:13:28.294230220 +0300
@@ -43,24 +43,4 @@
         - name: {{ $serviceName }}
           port: {{ $servicePort }}
 {{- end }}
----
-apiVersion: security.istio.io/v1
-kind: AuthorizationPolicy
-metadata:
-  name: {{ template "balancer.name" . }}-auth
-  labels:
-    {{- include "balancer.labels" . | nindent 4 }}
-spec:
-  selector:
-    matchLabels:
-      app: {{ template "balancer.name" . }}
-  action: DENY
-  rules:
-    - to:
-        - operation:
-            ports: ["{{ $servicePort }}"]
-            paths: ["/balancer*"]
-      when:
-        - key: request.headers[authorization]
-          values: ["*"]
 {{- end }}
```

После загрузки нового дистро применяем патч:

```bash
patch elma365/charts/balancer/templates/httproute.yaml < balancer-httproute.patch.2026.4
```

# Переход на Cilium Gateway (Hub)

<p class="callout warning">Статья в разработке. Ищем пути поднятия системы</p>

Ранее мы [рассмотрели](https://kb.randmgroup.ru/books/on-premises-helm/page/perexod-na-cilium-gateway "Переход на Cilium Gateway") переход для редакции Enterprise для single node. А теперь рассмотрим редакцию Hub на 4х узлах кластера.

Итак мы имеем кластер следующего вида:

```
# k8s status
cluster status:           ready
control plane nodes:      192.168.56.151:6400 (voter), 192.168.56.152:6400 (voter), 192.168.56.153:6400 (voter)
high availability:        yes
datastore:                etcd
network:                  enabled
dns:                      enabled at 10.152.183.211
ingress:                  disabled
load-balancer:            enabled, L2 mode
local-storage:            enabled at /var/snap/k8s/common/rawfile-storage
gateway                   enabled

# kubectl get nodes -A -owide
NAME    STATUS   ROLES                  AGE    VERSION    INTERNAL-IP      EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION   CONTAINER-RUNTIME
hc1n1   Ready    control-plane,worker   189d   v1.33.11   192.168.56.151   <none>        Ubuntu 24.04.4 LTS   6.17.13-19-pve   containerd://1.7.31
hc1n2   Ready    control-plane,worker   189d   v1.33.11   192.168.56.152   <none>        Ubuntu 24.04.4 LTS   6.17.13-19-pve   containerd://1.7.31
hc1n3   Ready    control-plane,worker   136d   v1.33.11   192.168.56.153   <none>        Ubuntu 24.04.4 LTS   6.17.13-19-pve   containerd://1.7.31
hc1n4   Ready    worker                 186d   v1.33.11   192.168.56.154   <none>        Ubuntu 24.04.4 LTS   7.0.14-5-pve     containerd://1.7.31
```

При этом как видно у нас уже включен load-balancer, т.к. у нас ранее установлен portainer в LB варианте:

```shell
portainer        service/portainer-agent                                      LoadBalancer   10.152.183.27    192.168.56.170   9001:31417/TCP                                     186d
portainer        service/portainer-agent-headless                             ClusterIP      None             <none>           <none>                                             186d
```

Создаем gateway из прошлой статьи. У нас подцепился нужный IP из преднастроенного пула:

```shell
kube-system      cilium-gateway-cilium-gateway                        LoadBalancer   10.152.183.249   192.168.56.171   80:31186/TCP                                       15s
```

Применяем патч и обновляет ELMA365:

```bash
helm upgrade --install elma365 ./elma365 -f values-elma365.yaml --timeout=30m --debug --wait
```

И вот больше нет доступа к нашим ресурсам. Смотрим что там у нас с HTTPRoute:

```bash
# kubectl get httproute -A
NAMESPACE   NAME             HOSTNAMES           AGE
default     balancer         ["hc1.rmg365.ru"]   11m
default     fileprotection   ["hc1.rmg365.ru"]   11m
default     front            ["hc1.rmg365.ru"]   11m
default     hydra-adaptor    ["hc1.rmg365.ru"]   11m
default     linkhub          ["hc1.rmg365.ru"]   11m
default     main             ["hc1.rmg365.ru"]   11m
default     main-publicapi   ["hc1.rmg365.ru"]   11m
default     notifier         ["hc1.rmg365.ru"]   11m
default     notigate         ["hc1.rmg365.ru"]   11m
default     vahter           ["hc1.rmg365.ru"]   11m
default     web-forms        ["hc1.rmg365.ru"]   11m
```

<p class="callout danger">Возвращаемся к танцам с бубном касательно тенантов - это периодическим еняется от версии к версии...</p>

<p class="callout info">... в процессе написания</p>

# Ошибка при вызове Web API

После обновления на релиз 2026.1 вариант Helm может перестать работать Web API. Чтобы решить эту проблему необходимо проверить наличие в values-elma365.yaml записи ingressClassName и если ее нет то добавить:

```yaml
  # домен или ip адрес, по которому будет доступна система
  # host: 'host.randm.ru'
  host: 'host.rmg365.ru'
  ingress:
    # включение host в ingress (значение берется из host)
    # для установленного s3 minio через чарты elma365-extensions, укажите в блоке minio 
    # значение в параметре hosts в файле values-extensions.yaml
    ingressClassName: nginx
```

<p class="callout warning">До версии 2026.1 этого не требовалось</p>

# Проблемы

# Error: Kubernetes cluster unreachable: Get https://127.0.0.1:16443

После обновления сертификатов в кубике возможна неработоспособность helm - появляется ошибка или с неверным сертификатом или что то с портом.

Необходимо обновить файл конфигурации кубика:

```bash
microk8s.kubectl config view --raw > $HOME/.kube/config
```

После этого helm должен ожить.

# Hub

# 2025.8.6+: Доступ к площадкам

Начиная с данной версии вырезано в Ingress прописывание тенантов и как следствие система перестала работать как надо.  
Чтобы это вылечить, необходимо:  
в values-elma365.yaml указать:

```yaml
global:
  host: 'myhubdomain.ru'
  ingress:
    hostEnabled: false
```

В реверс прокси для каждого адреса тенанта добавить в хидер:  
X-Company &lt;tenant&gt;  
где, &lt;tenant&gt; для конкретного названия, которое будет добавлено к полному имени &lt;tenant&gt;.myhubdomain.ru

edition: hub (solution: hub на текущий момент в природе не существует )

секцию hub прописываем как и раньше для каждого тенанта

если надо включить фишки хаб сервисов, то hubEnabled: true

# 2026.1-2026.3: Не создаются площадки

Начиная с указанной версии перестали создаваться и обновляться площадки. Это связано с некорректным значением в чарте дистрибутива. Для устранения данной проблемы необходимо создать следующий патч create-company.patch:

```diff
--- elma365/templates/create-company.yaml	2026-04-10 11:06:27.000000000 +0300
+++ ./create-company.yaml	2026-04-08 08:20:01.000000000 +0300
@@ -1,4 +1,5 @@
 {{- if eq .Values.global.solution "onPremise" }}
+{{- if or (eq .Values.global.edition "standard") (eq .Values.global.edition "enterprise") }}
 apiVersion: batch/v1
 kind: Job
 metadata:
@@ -57,7 +58,7 @@
   activeDeadlineSeconds: {{ default 1800 .Values.global.activeDeadlineSeconds }}
   ttlSecondsAfterFinished: 120
 
-{{- else if eq .Values.global.solution "hub" }}
+{{- else if eq .Values.global.edition "hub" }}
 
 {{- range $index, $job := .Values.global.hub }}
 ---
@@ -206,3 +201,4 @@
 data: {}
 
 {{- end }}
+{{- end }}
```

После чего применить его:

```bash
patch -u elma365/templates/create-company.yaml < create-company.patch
rm -f elma365/templates/create-company.yaml.orig
```

# 2026.4+: Не создаются площадки

Начиная с указанной версии перестали создаваться и обновляться площадки. Это связано с некорректным значением в чарте дистрибутива. Для устранения данной проблемы необходимо создать следующий патч create-company.patch:

```diff
--- elma365/templates/create-company.yaml	2026-05-14 14:35:38.000000000 +0300
+++ ./create-company.yaml	2026-05-17 09:53:59.345172000 +0300
@@ -1,4 +1,5 @@
 {{- if eq .Values.global.solution "onPremise" }}
+{{- if or (eq .Values.global.edition "standard") (eq .Values.global.edition "enterprise") }}
 apiVersion: batch/v1
 kind: Job
 metadata:
@@ -64,7 +65,7 @@
   activeDeadlineSeconds: {{ default 1800 .Values.global.activeDeadlineSeconds }}
   ttlSecondsAfterFinished: 120
 
-{{- else if eq .Values.global.solution "hub" }}
+{{- else if eq .Values.global.edition "hub" }}
 
 {{- range $index, $job := .Values.global.hub }}
 ---
@@ -83,9 +84,6 @@
   template:
     metadata:
       labels:
-        {{- if .Values.global.helmLibrary }}
-        {{- include "helm-library.deployment.podLabels" . | nindent 8 }}
-        {{- end }}
         app: hub
         tier: elma365
         sidecar.istio.io/inject: "false"
@@ -208,3 +206,4 @@
 data: {}
 
 {{- end }}
+{{- end }}
```

После чего применить его:

```bash
patch -u elma365/templates/create-company.yaml < create-company.patch
```