Kubernetes
July 2, 2023

Kuber for quality

Kuber for quality.

Немного о Kubernetes и контейнерах.

Kubernetes - это оркестратор контейнеров. Это то определение которое написано в Википедии. И в нём есть два не очень применимых к IT слова - оркестратор и контейнеры. Давайте поподробнее их рассмотрим.

Контейнеры, с точки зрения IT в общем и Linux систем в частности, это некая изолированная область в операционной системе. У него обособлены от основной системы: файловая система, сетевой стек, дисковое пространство, оперативная память, процессор. Чем-то всё это похоже на виртуальные машины. Но есть отличие - в контейнерах для запуска программ (приложений) нет необходимости устанавливать операционную систему. Там располагаются только сами исполняемые файлы и всё, что необходимо для их запуска: бинарники, библиотеки, зависимые программы. Это позволяет сократить издержки ресурсов на поддержку работу ОС, уменьшает время загрузки приложения и увеличивает стабильность его работы.
Итак, резюмируя. Контейнеры - это выделенная область в ОС, в которой находится какое-то приложение и все необходимые для его запуска компоненты.
Использование контейнеров нашло широкое применение в последние несколько лет. Их облюбовали программисты, тестировщики, системные администраторы. Они упрощают и ускоряют их работу. Также с появлением контейнеров появилась и новая специальность - DevOps. Это специалист, который хорошо понимает как работают контейнеры, помогает программисту и системному администратору найти общий язык и часто совмещает эти функции. О специалисте DevOps есть даже прекрасная шутка:
"Что общего между девопсом и бомжом?"
"Тот и другой ковыряются в контейнерах."

Понятие оркестрации должно быть хорошо известно любителям музыки. В оркестре есть много разных инструментов и дирижёр, который ими управляет. Это позволяет разным инструментам слаженно играть красивую симфонию.

Kubernetes можно сравнить с дирижёром. Только он управляет не музыкальными инструментами, а контейнерами. Он знает какой контейнер где находится, ресурсы которые он потребляет, какое приложение на нём запущено, жив ли он. Также он позволяет переносить контейнеры с одной виртуальной машины на другую в случае поломки оной. Это гарантирует независимость работы приложения от работы операционной системы ВМ и самой ВМ. Также это гарантирует работу приложения от поломки физического сервера, на котором расположены ВМ.

В Kubernetes есть множество разных абстракций, управляющих работой приложения и контейнера. Разобраться в них не просто, но мы постараемся вкратце их описать и дать базовое представление о них. К базовым абстракциям Kubernetes относятся: Pod, Namespace, Deployment, DaemonSet, StatefulSet, Service, Ingress, Secret, ConfigMap, PV/PVC.
Про них мы поговорим далее.

Namespace.

Первая абстракция, о которой поговорим - это namespace. Её задача - это группировка ресурсов. В namespace находятся почти все ресурсы Kubernetes.
Для понимания его проще всего сравнить с каталогами, которые хранят всякие разные файлы. Только namespace будут содержать не файлы, а: Pod, Namespace, Deployment, DaemonSet, StatefulSet, Service, Ingress, Secret, ConfigMap, PVC.
Также, в отличие от каталогов, namespace не может содержать в себе другой namespace. То есть нет вложенности.

При создании кластера Kubernetes создаются несколько стандартных namespace: default, kube-system, kube-public. В namespace default предлагается складывать ресурсы пользователю. В namespace kube-system находятся системные ресурсы кластера. В namespace kube-public лежат ресурсы доступные всем пользователям кластера.

Также можно самим создавать свои namespace. Для этого можно воспользоваться утилитой kubectl. Она используется для взаимодействия с API кластера.

Команда для создания namespace.

kubectl create ns testing

Эта команда создаёт namespace с именем testing.

Особенности namespace.

  1. Namespace используются для группировки других абстракций Kubernetes;
  2. Namespace используются для логического разделения ресурсов разных команд. Например, у команды mdb свой namespace, у команды тестировщикой свой, у фронтендеров свой. Свои namespace для окружения prod/preprod/testing;
  3. У namespace нет вложенности;
  4. Namespace можно ограничить по ресурсам. Например, сколько выделено ресурсов cpu/ram каждому namespace. Сколько можно создать подов/деплойментов/сервисов/ингрессов/etc;
  5. Имена ресурсов уникальны в пределах одного namespace. В разных namespace имена ресурсов могут совпадать;
  6. При создании кластера Kubernetes по-умолчанию создаются несколько namespace: default, kube-system, kube-public;
  7. Namespace можно создать/удалить.

Для удаления namespace можно воспользоваться командой.

kubectl delete ns testing

Pod.

Kubernetes не умеет работать с контейнерами напрямую. Минимальной, базовой, ресурсной единицей с которой он работает - это Pod. Контейнеры с полезной нагрузкой в виде приложений находятся в подах кластера.

Pod - это эфемерная сущность. Это запрос на создание контейнера с определённой конфигурацией, определёнными ресурсами и пробами.

Особенности подов.

1. Под - это минимальная объектная единица в кластере k8s. Kubernetes управляет не контейнерами напрямую, а подами.

2. Внутри одного пода могут располагаться несколько контейнеров с полезной нагрузкой. К примеру, nginx и postgresql. Но это абсолютно не продовое решение. Ведь если с подом что-то произойдёт, то погибнут сразу оба приложения. Также такое решение плохо масштабируется. Даже если сделать несколько одинаковых подов, то у каждого пода будет свой экземпляр базы данных и обратного прокси.
Поэтому такие сервисы лучше разносить по разным подам.

3. Под не может находиться вне какого-то пространства имён. Иными словами он всегда будет частью какого-то Namespace.

4. Под физически расположен на нодах кластера.

5. Поды, находящиеся на ноде кластера, совместно используют сетевой стек данной ноды.

6. У подов есть много разных состояний. Об этом поговорим ниже.

7. К подам могут монтироваться постоянные тома, для хранения данных, которые необходимо сохранить.

8. Кластер k8s управляет подами самостоятельно. Так он может перенести под с ноды на ноду без вмешательства со стороны человека.

9. Каждому поду присваивается свой уникальный ip адрес в сети кластера.

10. Поды предоставляют контейнерам доступ к сети и хранилищу.

11. Стоит разделять проблемы пода и проблемы с контейнером внутри данного пода.

12. Под - это не масштабируемый ресурс. Имеется ввиду, если мы создаём просто под, без управляющего им контроллера.

13. Поды зависят от ресурсов и лимитов в кластере. Если под будет запрашивать для своей работы ресурсов больше чем есть в кластере, то он будет ожидать когда эти ресурсы освободятся.

14. Для проверки того живы ли контейнеры внутри пода или нет используются специальные пробы (Liveness Probe и Readness Probe).

15. В случае пересоздания пода, вся информация в нём удаляется. Поэтому нет какого-то большого смысла подключаться к поду и что-то там устанавливать или править настройки.

16. Для управления настройками прилжений внутри контейнера в поде существует специальная абстракция - ConfigMap.

17. Если для работы пода нужна секретная информация, например, ключи доступа или пароли, то используется специальная сущность - Secret.

18. В современном мире поды вручную никто не создаёт. Для создания подов используются контроллеры. Например, Deployment, StatefulSet, DaemonSet.

19. У пода в обязательном порядке есть имя и спецификация контейнера с приложением внутри.

20. В манифесте любой сущности в кластере k8s обязательно есть версия его api (apiVersion), тип к которому относится сущность (kind), метаинформация о сущности (metadata) и сама спецификация (spec). Это обязательные параметры. При создании спецификации пода посмотрим на них внимательнее.

Из написанного выше становится ясно, что под - это базовая сущность, с которой работают множество других сущностей в кластере. Я здесь перечислил далеко не всё. Постепенно будем изучать их все.

Но что такое под в физическом смысле? Человек видит спецификацию на его создание, может посмотреть статус пода. Но что же такое сам под. В физическом смысле под - это запрос на создание и запуск контейнера на определённом узле (ноде) кластера k8s.

Под может находиться в одной из следующих фаз.

  • Pending (ожидание): API Server создал ресурс пода и сохранил его в etcd, но под ещё не был запланирован, а образы его контейнеров — не получены из реестра;
  • Running (функционирует): под был назначен узлу и все контейнеры созданы kubelet'ом;
  • Succeeded (успешно завершён): функционирование всех контейнеров пода успешно завершено и они не будут перезапускаться;
  • Failed (завершено с ошибкой): функционирование всех контейнеров пода прекращено и как минимум один из контейнеров завершился со сбоем;
  • Unknown (неизвестно): API Server не смог опросить статус пода, обычно из-за ошибки во взаимодействии с kubelet.

Пример спецификации пода.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    args:
    - sleep
    - "3600"
EOF

При использовании yaml манифеста следует помнить, что отступы здесь имеют значение. Количество отступов для объектов одного уровня должно быть одинаковым.

В данной спецификации четыре верхнеуровненых элемента: apiVersion, kind, metadata, spec.

Посмотреть какие версии api поддерживает кластер можно с помощью команды:

kubectl api-resources

Посмотреть информацию о подах.

# Посмотреть информацию о подах в текущем namespace
kubectl get po

# Посмотреть информацию о подах во всех namespace
kubectl get po -A

# Посмотреть информацию о подах в namespace kube-system
kubectl get po -n kube-system

# Посмотреть информацию о подах в текущем namespace в расширенном формате
kubectl get po -owide

# Посмотреть информацию о подах в текущем namespace в формате yaml
kubectl get po -oyaml

# Посмотреть информацию о подах в текущем namespace в формате json
kubectl get po -ojson

# Посмотреть информацию о подах в namespace kube-system в расширенном формате yaml
kubectl get po -n kube-system -owide -oyaml

Посмотреть логи пода.

kubectl logs cam-nginx

Зайти внутрь пода.

kubectl exec --tty --stdin cam-nginx -- /bin/bash

Посмотреть расширенную информацию о поде.

kubectl describe po cam-nginx

# Также расширенную информацию покажут команды
kubectl get po cam-nginx -oyaml
kubectl get po cam-nginx -ojson

Под также можно создать через команду run.

# Пример пода, используемого для диагностики DNS.
kubectl run jessie-dnsutils --image=k8s.gcr.io/jessie-dnsutils --restart=Never --command sleep infinity
# Пример ещё одного тестового пода.
kubectl run -i -t --rm --image amouat/network-utils test bash

Ресурсы пода.

При создании пода можно прописать какие ресурсы ему выделяются при его создании и сколько ресурсов ноды он может отхапать при увеличении нагрузки.

Для их указания существует блок resources.

cpu: 300m memory: 300Mi limits: memory: 500Mi

resources: 
  requests:
    cpu: 300m
    memory: 300Mi
  limits:
    memory: 500Mi

Использование cpu здесь указано в милиядрах. А memory в мебибайтах.
Указание лимитов на cpu является дурной практикой. Поэтому без неё обойдёмся.

Спецификация пода с реквестами и лимитами.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
  namespace: default
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:latest
    resources: 
      requests:
        cpu: 300m
        memory: 300Mi
    limits:
      memory: 500Mi
    args:
    - sleep
    - "3600"
EOF

QOS.

С реквестами и лимитами напрямую связаны QOS классы.

Поды не равны между собой по значимости. Одни поды более ценные, другие менее ценные. В тоже время ресурсы нод ограничены. Иными словами может сложиться ситуация когда кластеру придётся выбирать какой под запустить, а какой остановить из-за нехватки ресурсов. Или не выдать поду ресурсов. Чтобы управлять процессом значимости подов и придумали QOS классы.

Также QOS класс влияет на то, какие поды будут убиты OOM Killer, если памяти на нодах будет не хватать. Поведение подов при этом будет подчиняться тому что указано в поле restartPolicy.

У каждого пода есть свой класс QOS. Неважный, важный и самый важный.

Существует три QOS класса:

1. BestEffort — самый низкоприоритетный класс. Он назначается объектам, у которых не указаны requests и limits. В самом негативном случае такие объекты могут вообще не получить ресурсов. Они же — первые кандидаты на перемещение на другой узел при перераспределении ресурсов. Это QOS класс по-умолчанию.

2. Burstable — почти как Guaranteed, только с заданными requests < limits. И достаточно только одного ресурса CPU or Memory.

3. Guaranteed — назначается объектам с заданными requests = limits. Нужно задать эти значения для каждого контейнера, при этом запрошенное и лимитируемое значение cpu и ram должно быть идентичным. У этого класса наивысший приоритет.

Pod c QOS классом BestEffort:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
  labels:
    app: nginx
    environment: preprod
spec:
  containers:
  - image: nginx:latest
    name: cameda-nginx
    ports:
      - containerPort: 80
EOF
Кусочек дескрайба этого пода
QoS Class:                   BestEffort
Node-Selectors:              <none>

Pod c QOS классом Burstable:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
spec:
  containers:
  - image: nginx:latest
    name: cam-nginx
    resources:
      requests:
        cpu: 200m
        memory: 100Mi
      limits:
        cpu: 300m
        memory: 150Mi 
EOF
Кусочек дескрайба этого пода
QoS Class:                   Burstable
Node-Selectors:              <none>

Pod c QOS классом Guaranteed:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
spec:
  containers:
  - image: nginx:latest
    name: cam-nginx
    resources:
      requests:
        cpu: 300m
        memory: 150Mi
      limits:
        cpu: 300m
        memory: 150Mi 
EOF
Кусочек дескрайба этого пода
QoS Class:                   Guaranteed
Node-Selectors:              <none>

Probe.

Пробы - это хелсчеки состояния ресурсов. С их помощью можно проверить жив ли контейнер в поде или ресурс в контейнере.

Liveness Probe - контролирует состояние приложения во время его жизни. Работает постоянно.

Readness Probe - проверяет готово ли приложение принимать трафик. Если не готово, оно убирается из балансировки. Работает постоянно.

Events.

При просмотре дескрайба особое внимание необходимо уделить блоку events. Он будет находиться в самом его низу.

Выглядеть он будет примерно так:

Events:
  Type    Reason   Age                 From     Message
  ----    ------   ----                ----     -------
  Normal  Pulling  24m (x3 over 144m)  kubelet  Pulling image "nginx:latest"
  Normal  Created  24m (x3 over 144m)  kubelet  Created container nginx
  Normal  Started  24m (x3 over 144m)  kubelet  Started container nginx
  Normal  Pulled   24m                 kubelet  Successfully pulled image "nginx:latest" in 1.245822227s

Если с подом всё хорошо и он находится в состоянии RUNNING/COMPLETED, то в эвентах не будет ошибок. Но если под будет находиться в состоянии Error/Failed/CrashLoopBackOff/Init 0:1/Terminating, то там часто будет указана ошибка из-за которой под не работоспособен.

Поэтому, при траблшутинге, изучение эвентов - это то, с чего стоит начинать.

Node.

Поды шедулятся на воркер ноды кластера. Нода - это обычно ВМ, на которой расположена полезная нагрузка кластера в виде контейнеров. Здесь важно понимать, что физически на ноде будут запущены контейнеры. А в API k8s будут отображаться поды. Всё потому что k8s не умеет напрямую в контейнеры, а ноды не работают только с ними. Но для дальнейшего упрощения я буду говорить, что на нодах тоже поды :)

Ноды в кластере k8s бывают двух типов: воркер ноды, на которых расположена вся полезная нагрузка в виде подов с приложениями и мастер ноды, на которых расположены поды, управляющие работой кластера. О мастер нодах поговорим чуть позже.

Посмотреть информацию о том, сколько нод есть в кластере и их названия можно, используя команду:

kubectl get no

Пример вывода:

kubectl get no
NAME                        STATUS   ROLES    AGE   VERSION
cl1k0hr81fu4s6fuqlue-ahoq   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-ejuj   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-etyf   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-iqep   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-ucaj   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-ugeb   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-uguf   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-yguq   Ready    <none>   26d   v1.24.8
cl1k0hr81fu4s6fuqlue-ynip   Ready    <none>   26d   v1.24.8

В расширенном варианте выводится также информация об её адресе, версии ОС и ядра:

kubectl get no -owide
kubectl get no -owide
NAME                        STATUS   ROLES    AGE   VERSION   INTERNAL-IP   EXTERNAL-IP       OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
cl1k0hr81fu4s6fuqlue-ahoq   Ready    <none>   26d   v1.24.8   10.130.0.15   51.250.35.160     Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-ejuj   Ready    <none>   26d   v1.24.8   10.128.0.54   158.160.107.155   Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-etyf   Ready    <none>   26d   v1.24.8   10.130.0.18   51.250.42.245     Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-iqep   Ready    <none>   26d   v1.24.8   10.128.0.16   158.160.107.128   Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-ucaj   Ready    <none>   26d   v1.24.8   10.129.0.15   51.250.25.154     Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-ugeb   Ready    <none>   26d   v1.24.8   10.129.0.31   158.160.18.145    Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-uguf   Ready    <none>   26d   v1.24.8   10.129.0.40   158.160.31.142    Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-yguq   Ready    <none>   26d   v1.24.8   10.128.0.46   158.160.96.199    Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18
cl1k0hr81fu4s6fuqlue-ynip   Ready    <none>   26d   v1.24.8   10.130.0.17   51.250.39.185     Ubuntu 20.04.5 LTS   5.4.0-139-generic   containerd://1.6.18

Если сделать дескрайб ноды, то там можно увидеть какие поды на ней работают.

Пример:

Non-terminated Pods:          (12 in total)
  Namespace                   Name                                         CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                         ------------  ----------  ---------------  -------------  ---
  default                     csi-s3-dhl2x                                 0 (0%)        0 (0%)      0 (0%)           0 (0%)         26d
  default                     filebeat-filebeat-42xz8                      100m (5%)     0 (0%)      100Mi (3%)       0 (0%)         6d22h
  default                     ingress-nginx-controller-5cdbcc9966-859gx    100m (5%)     0 (0%)      90Mi (3%)        0 (0%)         11d
  default                     yc-alb-ingress-controller-hc-rkzjm           100m (5%)     0 (0%)      200Mi (7%)       200Mi (7%)     6d18h
  kube-system                 cilium-l2hv9                                 100m (5%)     0 (0%)      100Mi (3%)       0 (0%)         26d
  kube-system                 coredns-558bcd7559-65bpm                     100m (5%)     100m (5%)   128Mi (4%)       128Mi (4%)     3d20h
  kube-system                 hubble-ui-86889fdc75-swwth                   0 (0%)        0 (0%)      0 (0%)           0 (0%)         11d
  kube-system                 ip-masq-agent-kwzln                          10m (0%)      0 (0%)      16Mi (0%)        0 (0%)         26d
  kube-system                 node-local-dns-5nww5                         30m (1%)      0 (0%)      50Mi (1%)        0 (0%)         25d
  kube-system                 npd-v0.8.0-kgfgf                             20m (1%)      200m (10%)  20Mi (0%)        100Mi (3%)     26d
  kube-system                 yc-disk-csi-node-v2-wrd5p                    30m (1%)      600m (31%)  96Mi (3%)        600Mi (22%)    26d
  kubecost                    kubecost-prometheus-node-exporter-rvvsh      0 (0%)        0 (0%)      0 (0%)           0 (0%)         6d11h

Сколько ресурсов аллоцировано:

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                590m (30%)   900m (46%)
  memory             800Mi (29%)  1028Mi (37%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-1Gi      0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)

ConfigMap.

Данная абстракция Kubernetes предназначена для создания кастомных файлов конфигурации у приложения и передачи их в под. То есть для приложения в поде можно заранее подготовить нужную конфигурацию и при создании пода эту конфигурацию подложить.

Пример. Создадим кастомную конфигурацию для nginx сервера.

Файл /etc/nginx/site-available/default

server {
  listen 80 default_server;
  server_name cameda.ru;
  root /var/www/html/cameda.ru;
  access_log off;

  location / {
    index.html;
  }
###########################################################################
  location ~ /\.ht {
    deny all;
  }
}

Создадим ConfigMap из этого файла.

kubectl create cm nginx-config --from-file /etc/nginx/site-available/default

Создадим под с прикрученным ConfigMap.

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
  namespace: default
spec:
  containers:
  - name: cam-nginx
    image: nginx:latest
    resources:
      requests:
        cpu: 300m
        memory: 300Mi
      limits:
        memory: 500Mi
    volumeMounts:
    - name: nginx-configmap
      mountPath: /etc/nginx/sites-available
      readOnly: true
  volumes:
  - name: nginx-configmap
    configMap:
        name: nginx-config
EOF

Посмотреть информацию по ConfigMap.

kubectl get cm
kubectl describe cm nginx-config
kubectl describe po cam-nginx
kubectl exec --tty --stdin cam-nginx -- cat /etc/nginx/sites-available/default

Secret.

С помощью этой абстракции можно хранить чувствительные данные в защищенном хранилище. Например, сюда можно отнести пароли, OAUTH токены и тп.

Создать секрет можно с помощью команды.

kubectl create secret generic cam-secret --from-literal=cameda=goodPa$word

Создать секрет можно из файла.

echo -n 'cameda' > ./username.txt
echo -n 'goodPa$word' > ./password.txt
kubectl create secret generic cam-secret2 --from-file=./username.txt --from-file=./password.txt

Пример с использованием ssh ключа.

kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/Users/cameda/.ssh/id_rsa --from-file=ssh-publickey=/Users/cameda/.ssh/id_rsa.pub

После того как прокинули в под ssh ключ, нужно будет поменять права на приватный ключ (600). После этого его можно будет использовать.

Создадим под с прикрученным Secret.

cat << EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: cam-nginx
  namespace: default
spec:
  containers:
  - name: cam-nginx
    image: nginx:latest
    resources:
      requests:
        cpu: 300m
        memory: 300Mi
      limits:
        memory: 500Mi
    volumeMounts:
    - name: nginx-secret
      mountPath: /etc/secret
      readOnly: true
  volumes:
  - name: nginx-secret
    secret:
      secretName: cam-secret
EOF