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

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

Теперь для безопасности и гибкости конфигурация будет сохраняться в ConfigMap и Secret. А в дополнение к параметрам helm-чарта (Values) и секретам werf будут продемонстрированы подходы параметризации и переиспользования конфигурации, а также хранения конфиденциальных данных вместе с кодом в git-репозитории проекта.

Приложение в этой главе не предназначено для использования в production без доработки. Готовое к работе в production приложение мы получим в конце руководства.

Подготовка окружения

Подготовьте рабочее окружение согласно инструкциями главы “Подготовка окружения”, если это ещё не сделано.

Рабочее окружение работало, но перестало? Инструкции из этой главы не работают? Может помочь:

Работает ли Docker?

Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

Запустим приложение Docker Desktop. Приложению понадобится некоторое время для того, чтобы запустить Docker. Если никаких ошибок в процессе запуска не возникло, то проверим, что Docker запущен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

Запустим Docker:

sudo systemctl restart docker

Убедимся, что Docker запустился:

sudo systemctl status docker

Результат выполнения команды, если Docker успешно запущен:

● docker.service - Docker Application Container Engine
     Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
     Active: active (running) since Thu 2021-06-24 13:05:17 MSK; 13s ago
TriggeredBy: ● docker.socket
       Docs: https://docs.docker.com
   Main PID: 2013888 (dockerd)
      Tasks: 36
     Memory: 100.3M
     CGroup: /system.slice/docker.service
             └─2013888 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

dockerd[2013888]: time="2021-06-24T13:05:16.936197880+03:00" level=warning msg="Your kernel does not support CPU realtime scheduler"
dockerd[2013888]: time="2021-06-24T13:05:16.936219851+03:00" level=warning msg="Your kernel does not support cgroup blkio weight"
dockerd[2013888]: time="2021-06-24T13:05:16.936224976+03:00" level=warning msg="Your kernel does not support cgroup blkio weight_device"
dockerd[2013888]: time="2021-06-24T13:05:16.936311001+03:00" level=info msg="Loading containers: start."
dockerd[2013888]: time="2021-06-24T13:05:17.119938367+03:00" level=info msg="Loading containers: done."
dockerd[2013888]: time="2021-06-24T13:05:17.134054120+03:00" level=info msg="Daemon has completed initialization"
systemd[1]: Started Docker Application Container Engine.
dockerd[2013888]: time="2021-06-24T13:05:17.148493957+03:00" level=info msg="API listen on /run/docker.sock"

Теперь проверим, что Docker доступен и корректно настроен:

docker run hello-world

Результат успешного выполнения команды:

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:9f6ad537c5132bcce57f7a0a20e317228d382c3cd61edae14650eec68b2b345c
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

При возникновении проблем обратитесь к документации Docker для их устранения.

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

Запустим кластер minikube, уже настроенный в начале главы “Подготовка окружения”:

minikube start

Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl:

kubectl config set-context minikube --namespace=werf-guide-app

Результат успешного выполнения команды:

😄  minikube v1.20.0 on Ubuntu 20.04
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🎉  minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡  To disable this notice, run: 'minikube config set WantUpdateNotification false'

🔄  Restarting existing docker container for "minikube" ...
🐳  Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
    ▪ Using image registry:2.7.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying registry addon...
🔎  Verifying ingress addon...
🌟  Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄  Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default

Убедитесь, что вывод команды содержит строку:

Restarting existing docker container for "minikube"

Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.

Теперь запустите команду в фоновом PowerShell-терминале и не закрывайте его:

minikube tunnel --cleanup=true

Запустим кластер minikube, уже настроенный в начале главы “Подготовка окружения”:

minikube start --namespace werf-guide-app

Выставим Namespace по умолчанию, чтобы не указывать его при каждом вызове kubectl:

kubectl config set-context minikube --namespace=werf-guide-app

Результат успешного выполнения команды:

😄  minikube v1.20.0 on Ubuntu 20.04
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
🎉  minikube 1.21.0 is available! Download it: https://github.com/kubernetes/minikube/releases/tag/v1.21.0
💡  To disable this notice, run: 'minikube config set WantUpdateNotification false'

🔄  Restarting existing docker container for "minikube" ...
🐳  Preparing Kubernetes v1.20.2 on Docker 20.10.6 ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/google_containers/kube-registry-proxy:0.4
    ▪ Using image k8s.gcr.io/ingress-nginx/controller:v0.44.0
    ▪ Using image registry:2.7.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image docker.io/jettech/kube-webhook-certgen:v1.5.1
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🔎  Verifying registry addon...
🔎  Verifying ingress addon...
🌟  Enabled addons: storage-provisioner, registry, default-storageclass, ingress
🏄  Done! kubectl is now configured to use "minikube" cluster and "werf-guide-app" namespace by default

Убедитесь, что вывод команды содержит строку:

Restarting existing docker container for "minikube"

Если её нет, значит был создан новый кластер minikube вместо использования старого. В таком случае повторите установку рабочего окружения с minikube с самого начала.

Случайно удаляли Namespace приложения?

Если вы непреднамеренно удалили Namespace приложения, то необходимо выполнить следующие команды, чтобы продолжить прохождение руководства:

kubectl create namespace werf-guide-app
kubectl create secret docker-registry registrysecret \
  --docker-server='https://index.docker.io/v1/' \
  --docker-username='<имя пользователя Docker Hub>' \
  --docker-password='<пароль пользователя Docker Hub>'

Результат успешного выполнения команды:

namespace/werf-guide-app created
secret/registrysecret created
Ничего не помогло, окружение или инструкции по-прежнему не работают?

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

Подготовка репозитория

Обновим существующий репозиторий с приложением:

Выполним следующий набор команд в PowerShell:

cd ~/werf-guide/app

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/080_configuration/* .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Выполним следующий набор команд в Bash:

cd ~/werf-guide/app

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -rf ~/werf-guide/guides/examples/laravel/080_configuration/. .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Не работает? Попробуйте инструкции на вкладке “Начинаю проходить руководство с этой главы” выше.

Подготовим новый репозиторий с приложением:

Выполним следующий набор команд в PowerShell:

# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован
if (-not (Test-Path ~/werf-guide/guides)) {
  git clone https://github.com/werf/werf-guides $env:HOMEPATH/werf-guide/guides
}

# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app
rm -Recurse -Force ~/werf-guide/app
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/050_s3 ~/werf-guide/app

# Сделаем из директории ~/werf-guide/app git-репозиторий
cd ~/werf-guide/app
git init
git add .
git commit -m initial

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -Recurse -Force ~/werf-guide/guides/examples/laravel/080_configuration/* .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

Выполним следующий набор команд в Bash:

# Склонируем репозиторий с примерами в ~/werf-guide/guides, если он ещё не был склонирован
test -e ~/werf-guide/guides || git clone https://github.com/werf/werf-guides ~/werf-guide/guides

# Скопируем файлы приложения (пока без изменений) в ~/werf-guide/app
rm -rf ~/werf-guide/app
cp -rf ~/werf-guide/guides/examples/laravel/050_s3 ~/werf-guide/app

# Сделаем из директории ~/werf-guide/app git-репозиторий
cd ~/werf-guide/app
git init
git add .
git commit -m initial

# Чтобы увидеть, какие изменения мы собрались вносить далее в этой главе, заменим все файлы приложения
# в репозитории новыми, уже измененными файлами приложения, которые содержат описанные далее изменения.
git rm -r .
cp -rf ~/werf-guide/guides/examples/laravel/080_configuration/. .
git add .
git commit -m WIP
Посмотреть, какие именно изменения мы произведём
# Показать, какие файлы мы собираемся изменить
git show --stat
# Показать изменения
git show

ConfigMap и Secret

В Kubernetes есть специальные типы ресурсов ConfigMap и Secret, которые предназначены для отделения конфигурации, зависящей от среды и окружения, от образов контейнеров.

Оба ресурса позволяют хранить данные в парах “ключ-значение” и впоследствии использовать их как переменные окружения, аргументы командной строки или как файлы примонтированные в выбранный контейнер.

ConfigMap предназначен для хранения неконфиденциальных данных, в отличие от Secret, который используется для хранения различных типов секретов.

Подробнее про эти типы ресурсов можно почитать в документации Kubernetes (ConfigMaps, Secrets). Далее мы разберём частые случаи их использования на примере нашего приложения.

Хранение конфигурационных файлов приложения в ConfigMap

Сейчас конфигурационный файл nginx.conf копируется в образ прямо во время сборки. Из-за этого при каждом его изменении будут происходить пересборка образа и перезапуск Pod’ов. Также сейчас нет простой возможности шаблонизировать nginx.conf.

Эти проблемы решаются, если перенести .werf/nginx.conf в отдельный ConfigMap, для того, чтобы монтировать nginx.conf во время деплоя, а не добавлять файл при сборке:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    user nobody;
    worker_processes 1;
    pid /run/nginx.pid;

    events {
      worker_connections 1024;
    }

    http {
      include /etc/nginx/mime.types;
      default_type application/octet-stream;

      log_format json_combined escape=json
          '{'
            '"time_local":"$time_iso8601",'
            '"client_ip":"$http_x_forwarded_for",'
            '"remote_addr":"$remote_addr",'
            '"remote_user":"$remote_user",'
            '"request":"$request",'
            '"status":"$status",'
            '"body_bytes_sent":"$body_bytes_sent",'
            '"request_time":"$request_time",'
            '"http_referrer":"$http_referer",'
            '"http_user_agent":"$http_user_agent",'
            '"request_id":"$request_id"'
          '}';

      access_log /dev/stdout json_combined;

      server {
        listen 8080;
        server_name _;

        root /www;
        index index.php;
        error_page 404 /index.php;

        charset utf-8;

        add_header X-Frame-Options "SAMEORIGIN";
        add_header X-Content-Type-Options "nosniff";

        # Отдадим ассеты напрямую из файловой системы NGINX-контейнера.
        location / {
            try_files $uri $uri/ /index.php?$query_string;
        }

        location = /favicon.ico { access_log off; log_not_found off; }
        location = /robots.txt  { access_log off; log_not_found off; }

        # Все запросы, кроме запросов на получение ассетов, отправляются на php-fpm-бэкенд.
        location ~ \.php$ {
            fastcgi_pass unix:/var/run/php/php-fpm.sock;
            fastcgi_param DOCUMENT_ROOT   /app/public;
            fastcgi_param SCRIPT_FILENAME /app/public/$fastcgi_script_name;
            include fastcgi_params;
        }
      }
    }
apiVersion: v1 kind: ConfigMap metadata: name: nginx-conf data: nginx.conf: | user nobody; worker_processes 1; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; log_format json_combined escape=json '{' '"time_local":"$time_iso8601",' '"client_ip":"$http_x_forwarded_for",' '"remote_addr":"$remote_addr",' '"remote_user":"$remote_user",' '"request":"$request",' '"status":"$status",' '"body_bytes_sent":"$body_bytes_sent",' '"request_time":"$request_time",' '"http_referrer":"$http_referer",' '"http_user_agent":"$http_user_agent",' '"request_id":"$request_id"' '}'; access_log /dev/stdout json_combined; server { listen 8080; server_name _; root /www; index index.php; error_page 404 /index.php; charset utf-8; add_header X-Frame-Options "SAMEORIGIN"; add_header X-Content-Type-Options "nosniff"; # Отдадим ассеты напрямую из файловой системы NGINX-контейнера. location / { try_files $uri $uri/ /index.php?$query_string; } location = /favicon.ico { access_log off; log_not_found off; } location = /robots.txt { access_log off; log_not_found off; } # Все запросы, кроме запросов на получение ассетов, отправляются на php-fpm-бэкенд. location ~ \.php$ { fastcgi_pass unix:/var/run/php/php-fpm.sock; fastcgi_param DOCUMENT_ROOT /app/public; fastcgi_param SCRIPT_FILENAME /app/public/$fastcgi_script_name; include fastcgi_params; } } }

Теперь добавим этот ConfigMap в Deployment, примонтировав его как файл внутрь контейнера frontend:

...
- name: frontend
  image: {{ .Values.werf.image.frontend }}
  ports:
    - containerPort: 8080
      name: http
  volumeMounts:
    - mountPath: /var/run/php
      name: run-php
    - mountPath: /etc/nginx/nginx.conf
      subPath: nginx.conf
      name: nginx-conf
... - name: frontend image: {{ .Values.werf.image.frontend }} ports: - containerPort: 8080 name: http volumeMounts: - mountPath: /var/run/php name: run-php - mountPath: /etc/nginx/nginx.conf subPath: nginx.conf name: nginx-conf

Не забудем удалить более ненужный файл .werf/nginx.conf, а также команду копирования этого файла в образ во время сборки, после чего сборка образа frontend станет выглядеть так:

...
# NGINX-образ с публичными файлами.
FROM nginx:stable-alpine as frontend
WORKDIR /www

# Копируем публичные файлы из assets образа.
COPY --from=assets /app/public /www

EXPOSE 8080
... # NGINX-образ с публичными файлами. FROM nginx:stable-alpine as frontend WORKDIR /www # Копируем публичные файлы из assets образа. COPY --from=assets /app/public /www EXPOSE 8080

Перевыкат Deployment при изменении ConfigMap и Secret

По умолчанию, изменения в ConfigMap и Secret, примонтированных к Deployment, StatefulSet или DaemonSet, не приведут к перезапуску Pod’ов с новой конфигурацией. Чтобы Pod’ы обновились, их нужно аннотировать хеш-суммами всех используемых Pod’ом ConfigMap и Secret. Тогда при изменении ConfigMap и Secret аннотация изменится и Pod пересоздастся. Так выглядит аннотация с хеш-суммой ConfigMap с nginx.conf:

...
template:
  metadata:
    labels:
      app: werf-guide-app
    annotations:
      checksum/configmap-nginx: '{{ include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum }}'
... template: metadata: labels: app: werf-guide-app annotations: checksum/configmap-nginx: '{{ include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum }}'

Для каждого подключенного ConfigMap и Secret потребуется отдельная аннотация.

Авторы самоучителя предпочитают вместо аннотаций с хеш-суммами использовать операторы вроде stakater/Reloader, т.к. они проще, гибче и удобнее в работе.

Values

Использование helm-чарта для выката приложения даёт ряд преимуществ, например, возможность шаблонизировать манифесты. Ключевой встроенный объект шаблонизации — это Values, который предоставляет доступ к значениям, передаваемым в чарт из различных источников.

При использовании werf все данные передаваемые в chart можно условно разделить на:

Переиспользование конфигурации с Values и ConfigMap

Для упрощения конфигурации манифестов часто используемые параметры можно выносить в .helm/values.yaml:

...
mysql:
  storageSize: "100Mi"
... mysql: storageSize: "100Mi"

… и далее использовать в манифесте следующим образом:

...
volumeClaimTemplates:
- metadata:
    name: mysql-data
  spec:
    accessModes: ["ReadWriteOnce"]
    resources:
      requests:
        storage: "{{ .Values.mysql.storageSize }}"
... volumeClaimTemplates: - metadata: name: mysql-data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: "{{ .Values.mysql.storageSize }}"

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

Но особенно полезным будет перенос в .helm/values.yaml повторяющейся конфигурации, например переменных окружения для приложения, которые мы используем в нескольких местах сразу:

...
app:
  envs:
    APP_ENV: "production"
    LOG_CHANNEL: stderr
    LOG_STDERR_FORMATTER: Monolog\\Formatter\\JsonFormatter
    LOG_LEVEL: info
    DB_HOST: mysql
    DB_DATABASE: werf-guide-app
    AWS_ENDPOINT: "http://minio:9000"
    AWS_DEFAULT_REGION: us-east-1
    AWS_BUCKET: werf-guide-app
    AWS_USE_PATH_STYLE_ENDPOINT: "true"
... app: envs: APP_ENV: "production" LOG_CHANNEL: stderr LOG_STDERR_FORMATTER: Monolog\\Formatter\\JsonFormatter LOG_LEVEL: info DB_HOST: mysql DB_DATABASE: werf-guide-app AWS_ENDPOINT: "http://minio:9000" AWS_DEFAULT_REGION: us-east-1 AWS_BUCKET: werf-guide-app AWS_USE_PATH_STYLE_ENDPOINT: "true"

Теперь переменные окружения из .Values.app.envs можно либо подставлять в манифест в env контейнера, как было ранее, либо вынести эти переменные окружения в ConfigMap, который потом подключить в контейнер через envFrom.

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

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-envs
data:
  {{- range $key, $val := .Values.app.envs }}
  "{{ $key }}": "{{ $val }}"
  {{- end }}
apiVersion: v1 kind: ConfigMap metadata: name: app-envs data: {{- range $key, $val := .Values.app.envs }} "{{ $key }}": "{{ $val }}" {{- end }}

Подключаем этот ConfigMap через envFrom в Deployment как набор переменных окружения:

...
containers:
  - name: backend
    image: {{ .Values.werf.image.backend }}
    volumeMounts:
      - mountPath: /var/run/php
        name: run-php
    envFrom:
    - configMapRef:
        name: app-envs
... containers: - name: backend image: {{ .Values.werf.image.backend }} volumeMounts: - mountPath: /var/run/php name: run-php envFrom: - configMapRef: name: app-envs

Не забываем про аннотации с хеш-суммами, которые приведут к пересозданию Pod’ов при изменении ConfigMap:

...
template:
  metadata:
    labels:
      app: werf-guide-app
    annotations:
      checksum/configmap-nginx: '{{ include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum }}'
      checksum/configmap-app-envs: '{{ include (print $.Template.BasePath "/configmap-app-envs.yaml") . | sha256sum }}'
... template: metadata: labels: app: werf-guide-app annotations: checksum/configmap-nginx: '{{ include (print $.Template.BasePath "/configmap-nginx.yaml") . | sha256sum }}' checksum/configmap-app-envs: '{{ include (print $.Template.BasePath "/configmap-app-envs.yaml") . | sha256sum }}'

Аналогично этот ConfigMap подключается и в остальные ресурсы, в которых требуются эти переменные окружения.

Использование конфиденциальных данных с Values и Secret

Чтобы начать работу с секретами, сначала требуется сгенерировать симметричный ключ шифрования командой werf helm secret generate-secret-key. Но так как мы заранее подготовили и зашифровали секреты, то ключ шифрования мы сгенерировали за вас. Он находится в репозитории в файле .werf_secret_key и начнёт использоваться автоматически.

При работе с реальными приложениями ключ НЕЛЬЗЯ хранить в репозитории. Вместо этого рекомендуется передавать его через переменную окружения WERF_SECRET_KEY, храня его в безопасном месте. Подробнее про работу с ключами шифрования здесь.

Конфигурация приложения содержит данные, которые не должны храниться в незашифрованном виде в репозитории, например, логин и пароль от базы данных. Вместо того чтобы хранить логин и пароль в открытом виде в манифесте контроллера, мы будем хранить их зашифрованными в .helm/secret-values.yaml вместе с другими секретными параметрами:

app:
  secretEnvs:
    APP_KEY: 10000f02004508feddf9746539c6f937238dfa0bb1cdf3e628547721d868ad1ba5b5a9a97535d7a4d218355bd2a0df22972e90b2a2c91a0f1e754c2c0b40aa4145b44d9c202535f945d95e14f3bb20399b65
    DB_USERNAME: 1000eb0a862c8a80dec5e7cd5c179177e9fe68ecc55896a01f0f16f5212c4ad9522d
    DB_PASSWORD: 10001bd1c973a299589f796896bcdb9afce3840dd3990537a18fd453ed7b2dca6747
    AWS_ACCESS_KEY_ID: 10004ce322cc0d7b02845cfa4b2b2a1f8b9a24dbb1849b6229484af470374b97ca7c
    AWS_SECRET_ACCESS_KEY: 1000ee9f9f758763cdfef3a8e78565bb05b0364bb6b6b43fca06cd4016f149d965f2
mysql:
  secretEnvs:
    MYSQL_ROOT_PASSWORD: 10007d80d8b6dff5ef78b73866b2fc7c167615f5974fa9b4f68b44595b8c203be855
minio:
  secretEnvs:
    MINIO_ROOT_USER: 1000347c3348673966f1f6a0c037ad8bdd3cda7cfedc989b8a0edf7a1b97fad99e8c
    MINIO_ROOT_PASSWORD: 1000216183bc27254749b6012ed68bb6af7d298e6b64d63ade285ed5f5d5da202000
app: secretEnvs: APP_KEY: 10000f02004508feddf9746539c6f937238dfa0bb1cdf3e628547721d868ad1ba5b5a9a97535d7a4d218355bd2a0df22972e90b2a2c91a0f1e754c2c0b40aa4145b44d9c202535f945d95e14f3bb20399b65 DB_USERNAME: 1000eb0a862c8a80dec5e7cd5c179177e9fe68ecc55896a01f0f16f5212c4ad9522d DB_PASSWORD: 10001bd1c973a299589f796896bcdb9afce3840dd3990537a18fd453ed7b2dca6747 AWS_ACCESS_KEY_ID: 10004ce322cc0d7b02845cfa4b2b2a1f8b9a24dbb1849b6229484af470374b97ca7c AWS_SECRET_ACCESS_KEY: 1000ee9f9f758763cdfef3a8e78565bb05b0364bb6b6b43fca06cd4016f149d965f2 mysql: secretEnvs: MYSQL_ROOT_PASSWORD: 10007d80d8b6dff5ef78b73866b2fc7c167615f5974fa9b4f68b44595b8c203be855 minio: secretEnvs: MINIO_ROOT_USER: 1000347c3348673966f1f6a0c037ad8bdd3cda7cfedc989b8a0edf7a1b97fad99e8c MINIO_ROOT_PASSWORD: 1000216183bc27254749b6012ed68bb6af7d298e6b64d63ade285ed5f5d5da202000

Увидеть расшифрованными секреты хранящиеся в .helm/secret-values.yaml можно следующей командой:

werf helm secret values decrypt .helm/secret-values.yaml

Ожидаемый результат:

app:
  secretEnvs:
    APP_KEY: base64:GcPVmSxMZwsOJtNOJ9eVNNeU6B5buHuln93+w0TSvfE=
    DB_USERNAME: root
    DB_PASSWORD: password
    AWS_ACCESS_KEY_ID: minioadmin
    AWS_SECRET_ACCESS_KEY: minioadmin
mysql:
  secretEnvs:
    MYSQL_ROOT_PASSWORD: password
minio:
  secretEnvs:
    MINIO_ROOT_USER: minioadmin
    MINIO_ROOT_PASSWORD: minioadmin

Теперь нужно передать секреты из .helm/secret-values.yaml обратно в конфигурационный файл приложения. Для этого сначала передадим их в Secret-ресурс:

apiVersion: v1
kind: Secret
metadata:
  name: app-envs
type: Opaque
data:
  {{- range $key, $val := .Values.app.secretEnvs }}
  "{{ $key }}": "{{ $val | b64enc }}"
  {{- end }}
apiVersion: v1 kind: Secret metadata: name: app-envs type: Opaque data: {{- range $key, $val := .Values.app.secretEnvs }} "{{ $key }}": "{{ $val | b64enc }}" {{- end }}

… а затем примонтируем Secret в контейнеры как набор переменных окружения:

...
containers:
  - name: backend
    image: {{ .Values.werf.image.backend }}
    volumeMounts:
      - mountPath: /var/run/php
        name: run-php
    envFrom:
    - configMapRef:
        name: app-envs
    - secretRef:
        name: app-envs
... containers: - name: backend image: {{ .Values.werf.image.backend }} volumeMounts: - mountPath: /var/run/php name: run-php envFrom: - configMapRef: name: app-envs - secretRef: name: app-envs

Изменения в остальных файлах .helm/templates для вынесения секретной конфигурации в Secret аналогичны, поэтому приводить их здесь не будем.

Также, для хранения и монтирования секретных конфигурационных файлов целиком можно использовать Secret. Выглядит это так же, как использование ConfigMap для монтирования несекретных файлов конфигурации, описанное выше, только само содержание Secret должно храниться зашифрованным в файлах .helm/secret/... или в .helm/secret-values.yaml, подробнее здесь.

Больше информации по работе с секретами в документации.

Проверка работоспособности

Убедимся, что изменения в конфигурации не повлияли на работоспособность приложения:

werf converge --repo <имя пользователя Docker Hub>/werf-guide-app

Ожидаемый результат:

...
┌ ⛵ image backend
│ ┌ Building stage backend/dockerfile
│ │ backend/dockerfile  Sending build context to Docker daemon    533kB
│ │ backend/dockerfile  Step 1/19 : FROM php:8.0-fpm-alpine as base
│ │ backend/dockerfile   ---> 52c511f481c5
...
│ │ backend/dockerfile  Successfully built 91ec44f42d84
│ │ backend/dockerfile  Successfully tagged d972fcf1-88cc-4a11-a2b8-e76e397914f1:latest
│ ├ Info
│ │      name: golovinps/werf-guide-app:b12f6a22c119f88d808e9bf5daf5bb0eab8138f0539769e478647e70-1634037356083
│ │        id: 91ec44f42d84
│ │   created: 2021-10-12 14:15:55 +0300 MSK
│ │      size: 58.9 MiB
│ └ Building stage backend/dockerfile (19.73 seconds)
└ ⛵ image backend (24.98 seconds)

┌ ⛵ image frontend
│ ┌ Building stage frontend/dockerfile
│ │ frontend/dockerfile  Sending build context to Docker daemon    533kB
│ │ frontend/dockerfile  Step 1/29 : FROM php:8.0-fpm-alpine as base
│ │ frontend/dockerfile   ---> 52c511f481c5
...
│ │ frontend/dockerfile  Successfully built a61e4952387d
│ │ frontend/dockerfile  Successfully tagged b6cc14ad-f7d6-47d9-a53d-b53cd80838d3:latest
│ ├ Info
│ │      name: golovinps/werf-guide-app:55170630d763de2af7ac5f4ca4a3c1bdb4f98c9782e99fdd8b07d2bb-1634037415695
│ │        id: a61e4952387d
│ │   created: 2021-10-12 14:16:55 +0300 MSK
│ │      size: 9.4 MiB
│ └ Building stage frontend/dockerfile (77.28 seconds)
└ ⛵ image frontend (82.49 seconds)

┌ Waiting for release resources to become ready
│ ┌ Status progress
│ │ DEPLOYMENT                                                                                      REPLICAS          AVAILABLE           UP-TO-DATE
│ │ werf-guide-app                                                                                  1/1               1                   1
│ │ │   POD                                 READY        RESTARTS          STATUS
│ │ ├── guide-app-54d48f7d4-zvkqd           2/2          0                 Running
│ │ └── guide-app-6b8cf46f5b-zj4d9          2/2          0                 Terminating
│ │ STATEFULSET                                                                                     REPLICAS          READY               UP-TO-DATE
│ │ minio                                                                                           1/1               1                   1
│ │ mysql                                                                                           1/1               0->1                1                            ↵
│ │
│ │ │   POD                                 READY        RESTARTS          STATUS
│ │ └── 0                                   0/1          0                 ContainerCreating
│ └ Status progress
└ Waiting for release resources to become ready (30.34 seconds)

┌ Waiting for helm hook job/migrate-db termination
│ ┌ job/migrate-db po/migrate-db-vvtfd container/migrate-db logs
│ │ mysqld is alive
│ │ Nothing to migrate.
│ └ job/migrate-db po/migrate-db-vvtfd container/migrate-db logs
│
│ ┌ Status progress
│ │ JOB                                                                                             ACTIVE            DURATION            SUCCEEDED/FAILED
│ │ migrate-db                                                                                      0                 13s                 0->1/0
│ │ │   POD                                 READY        RESTARTS          STATUS
│ │ └── db-vvtfd                            0/1          0                 Running -> Completed
│ └ Status progress
└ Waiting for helm hook job/migrate-db termination (13.08 seconds)

┌ Waiting for helm hook job/setup-minio termination
│ ┌ Status progress
│ │ JOB                                                                                             ACTIVE            DURATION            SUCCEEDED/FAILED
│ │ setup-minio                                                                                     0                 13s                 0->1/0
│ │ │   POD                                 READY        RESTARTS          STATUS
│ │ └── minio-d8cmm                         0/1          0                 Running -> Completed
│ └ Status progress
└ Waiting for helm hook job/setup-minio termination (14.02 seconds)

Release "werf-guide-app" has been upgraded. Happy Helming!
NAME: werf-guide-app
LAST DEPLOYED: Tue Oct 12 14:17:12 2021
NAMESPACE: werf-guide-app
STATUS: deployed
REVISION: 20
TEST SUITE: None
Running time 155.26 seconds
... ┌ ⛵ image backend │ ┌ Building stage backend/dockerfile │ │ backend/dockerfile Sending build context to Docker daemon 533kB │ │ backend/dockerfile Step 1/19 : FROM php:8.0-fpm-alpine as base │ │ backend/dockerfile ---> 52c511f481c5 ... │ │ backend/dockerfile Successfully built 91ec44f42d84 │ │ backend/dockerfile Successfully tagged d972fcf1-88cc-4a11-a2b8-e76e397914f1:latest │ ├ Info │ │ name: golovinps/werf-guide-app:b12f6a22c119f88d808e9bf5daf5bb0eab8138f0539769e478647e70-1634037356083 │ │ id: 91ec44f42d84 │ │ created: 2021-10-12 14:15:55 +0300 MSK │ │ size: 58.9 MiB │ └ Building stage backend/dockerfile (19.73 seconds) └ ⛵ image backend (24.98 seconds) ┌ ⛵ image frontend │ ┌ Building stage frontend/dockerfile │ │ frontend/dockerfile Sending build context to Docker daemon 533kB │ │ frontend/dockerfile Step 1/29 : FROM php:8.0-fpm-alpine as base │ │ frontend/dockerfile ---> 52c511f481c5 ... │ │ frontend/dockerfile Successfully built a61e4952387d │ │ frontend/dockerfile Successfully tagged b6cc14ad-f7d6-47d9-a53d-b53cd80838d3:latest │ ├ Info │ │ name: golovinps/werf-guide-app:55170630d763de2af7ac5f4ca4a3c1bdb4f98c9782e99fdd8b07d2bb-1634037415695 │ │ id: a61e4952387d │ │ created: 2021-10-12 14:16:55 +0300 MSK │ │ size: 9.4 MiB │ └ Building stage frontend/dockerfile (77.28 seconds) └ ⛵ image frontend (82.49 seconds) ┌ Waiting for release resources to become ready │ ┌ Status progress │ │ DEPLOYMENT REPLICAS AVAILABLE UP-TO-DATE │ │ werf-guide-app 1/1 1 1 │ │ │ POD READY RESTARTS STATUS │ │ ├── guide-app-54d48f7d4-zvkqd 2/2 0 Running │ │ └── guide-app-6b8cf46f5b-zj4d9 2/2 0 Terminating │ │ STATEFULSET REPLICAS READY UP-TO-DATE │ │ minio 1/1 1 1 │ │ mysql 1/1 0->1 1 ↵ │ │ │ │ │ POD READY RESTARTS STATUS │ │ └── 0 0/1 0 ContainerCreating │ └ Status progress └ Waiting for release resources to become ready (30.34 seconds) ┌ Waiting for helm hook job/migrate-db termination │ ┌ job/migrate-db po/migrate-db-vvtfd container/migrate-db logs │ │ mysqld is alive │ │ Nothing to migrate. │ └ job/migrate-db po/migrate-db-vvtfd container/migrate-db logs │ │ ┌ Status progress │ │ JOB ACTIVE DURATION SUCCEEDED/FAILED │ │ migrate-db 0 13s 0->1/0 │ │ │ POD READY RESTARTS STATUS │ │ └── db-vvtfd 0/1 0 Running -> Completed │ └ Status progress └ Waiting for helm hook job/migrate-db termination (13.08 seconds) ┌ Waiting for helm hook job/setup-minio termination │ ┌ Status progress │ │ JOB ACTIVE DURATION SUCCEEDED/FAILED │ │ setup-minio 0 13s 0->1/0 │ │ │ POD READY RESTARTS STATUS │ │ └── minio-d8cmm 0/1 0 Running -> Completed │ └ Status progress └ Waiting for helm hook job/setup-minio termination (14.02 seconds) Release "werf-guide-app" has been upgraded. Happy Helming! NAME: werf-guide-app LAST DEPLOYED: Tue Oct 12 14:17:12 2021 NAMESPACE: werf-guide-app STATUS: deployed REVISION: 20 TEST SUITE: None Running time 155.26 seconds

Проверим доступность приложения:

curl http://werf-guide-app/ping

Ожидаемый результат:

pong

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