Файлы, упомянутые в главе

  • .helm/templates/deployment-frontend.yaml
  • .helm/templates/deployment-backend.yaml
  • .helm/templates/service-backend.yaml
  • .helm/templates/ingress.yaml
  • werf.yaml

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

  • сложных случаев с двумя приложениями на двух разных языках;
  • ситуации, когда есть более одного запускаемого процесса (например, сервис, отвечающий на HTTP-запросы, и worker);
  • ситуации, когда в одном репозитории находятся frontend и backend.

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

Добавим к нашему приложению Single Page Application-приложением на react которое отображает публичную часть. Наш подход будет очень похож на то, что делалось в главе Генерируем и раздаем ассеты, с одним существенным отличием: изменения в коде react сильно отделены от изменений в Java приложении. Как следствие — мы разнесём их в разные папки, а также в различные Pod-ы.

Мы рассмотрим вопрос организации структуры файлов и папок, соберём два образа: для Java приложения и для react приложения и сконфигурируем запуск этих образов в Kubernetes.

Структура файлов и директорий

Структура каталогов будет организована следующим образом:

├── .helm/
│   ├── templates/
│   └── values.yaml
├── backend/
├── frontend/
└── werf.yaml

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

А если получится слишком много информации в одном месте и станет сложно ориентироваться?

Helm обрабатывает все файлы, которые находятся в папке .helm/templates, а значит их может быть столько, сколько удобно вам. Для упрощения кода можно использовать общие блоки.

Кроме того werf.yaml также поддерживает Описание конфигурации в нескольких файлах и вынесение части кода в общие блоки.

Сборка приложений

На стадии сборки приложения нам необходимо правильно организовать структуру файла werf.yaml, описав в нём сборку двух приложений на разном стеке.

Мы соберём два образа: backend c Java-приложением и frontend c React-приложением. Для сборки последнего мы воспользуемся механизмом артефактов (и соберём артефакт frontend-build) — мы использовали подобное в главе Генерируем и раздаем ассеты.

Как конкретно?

Сборка образа backend аналогична ранее описанному базовому приложению с зависимостями, за исключением того, откуда берётся исходный код:

git:
- add: /backend
  to: /app
git: - add: /backend to: /app

Мы добавляем в собираемый образ только исходные коды, относящиеся к java приложению. Таким образом, пересборка этой части проекта не будет срабатывать, когда изменился только React-код.

Сборка для frontend приложения описана в файле werf.yaml как отдельный образ. Поскольку nodejs нужен только для сборки - соберем артефакт:

artifact: frontend-build
from: node:{{ .NODE_MAJOR }}
git:
- add: /frontend
  to: /app
artifact: frontend-build from: node:{{ .NODE_MAJOR }} git: - add: /frontend to: /app

И затем импортируем необходимые файлы в образ с nginx:

.helm/templates/deployment-frontend.yaml копировать имя копировать текст
---
image: frontend
from: nginx:alpine
import:
- artifact: frontend-build
  add: /app/build
  to: /www
  after: setup
--- image: frontend from: nginx:alpine import: - artifact: frontend-build add: /app/build to: /www after: setup

Конфигурация инфраструктуры в Kubernetes

Подготовленные приложения мы будем запускать отдельными объектами Deployment: таким образом в случае изменений только в одной из частей приложения будет перевыкатываться только эта часть. Создадим два отдельных файла для описания объектов: frontend.yaml и backend.yaml. В условиях, когда в одном сервисе меньше 15-20 объектов — удобно следовать принципу максимальной атомарности в шаблонах.

При деплое нескольких Deployment крайне важно правильно прописать selector-ы в Service и Deployment:

.helm/templates/service-backend.yaml копировать имя копировать текст
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Chart.Name }}-backend
spec:
  selector:
    app: {{ .Chart.Name }}-backend
--- apiVersion: v1 kind: Service metadata: name: {{ .Chart.Name }}-backend spec: selector: app: {{ .Chart.Name }}-backend
.helm/templates/deployment-backend.yaml копировать имя копировать текст
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}-backend
spec:
  selector:
    matchLabels:
      app: {{ .Chart.Name }}-backend
  template:
    metadata:
      labels:
        app: {{ .Chart.Name }}-backend
--- apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }}-backend spec: selector: matchLabels: app: {{ .Chart.Name }}-backend template: metadata: labels: app: {{ .Chart.Name }}-backend

Маршрутизация запросов будет осуществляться через Ingress:

  rules:
  - host: {{ .Values.global.ci_url }}
    http:
      paths:
      - path: /
        backend:
          serviceName: {{ .Chart.Name }}-frontend
          servicePort: 80
      - path: /api
        backend:
          serviceName: {{ .Chart.Name }}-backend
          servicePort: 8080
rules: - host: {{ .Values.global.ci_url }} http: paths: - path: / backend: serviceName: {{ .Chart.Name }}-frontend servicePort: 80 - path: /api backend: serviceName: {{ .Chart.Name }}-backend servicePort: 8080