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

  • .helm/templates/deployment.yaml
  • .helm/templates/registry-secret.yaml
  • .helm/templates/ingress.yaml
  • .helm/templates/service.yaml

В предыдущей главе мы описали IaC для сборки, теперь нужно описать IaC для запуска в Kubernetes. В нашем случае потребуются следующие объекты Kubernetes: Deployment, Service и Ingress.

Как быть, если я не знаю Kubernetes?

В самоучителе будет приведён исходный код инфраструктуры и вы сможете интуитивно догадаться, что там написано. Чтобы научиться писать подобный код самостоятельно, стоит воспользоваться обучающими материалами и/или, например, в официальной документацией Kubernetes. Видеокурсов и учебников, рассказывающих об объектах Kubernetes и их возможных настройках, на данный момент существует достаточно.

werf поддерживает весь функционал шаблонизатора Helm (werf использует вкомпилированный в него Helm для деплоя), а также предоставляет дополнительную функциональность. Подробнее в шаблонизации и правилах написания Kubernetes-объектов мы разберёмся позже, в главе «Конфигурирование инфраструктуры в виде кода», а пока — добьёмся, чтобы приложение заработало в реальном кластере.

Deployment

Объект Deployment позволяет создать объект Pod, который содержит в себе контейнеры с приложениями и управляет ими. У создаваемого нами Pod будет один контейнер — basicapp.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: basicapp
spec:
  selector:
    matchLabels:
      app: basicapp
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
  replicas: 1
  template:
    metadata:
      labels:
        app: basicapp
    spec:
      containers:
      - name: basicapp
        command: ["bundle","exec", "puma"]
        image: {{ .Values.werf.image.basicapp }}
        workingDir: /app
        ports:
        - containerPort: 3000
          protocol: TCP
        env:
        - name: "SQLITE_FILE"
          value: "app.db"
apiVersion: apps/v1 kind: Deployment metadata: name: basicapp spec: selector: matchLabels: app: basicapp revisionHistoryLimit: 3 strategy: type: RollingUpdate replicas: 1 template: metadata: labels: app: basicapp spec: containers: - name: basicapp command: ["bundle","exec", "puma"] image: {{ .Values.werf.image.basicapp }} workingDir: /app ports: - containerPort: 3000 protocol: TCP env: - name: "SQLITE_FILE" value: "app.db"

Обратите внимание на конструкцию image: {{ .Values.werf.image.basicapp }} — с помощью неё подставляется актуальное имя образа.

werf пересобирает контейнеры только при необходимости, если есть изменения в связанных файлах в Git или в конфигурации werf.yaml. При изменении образа меняется его тег, что приводит к перевыкату Pod’а с новым образом.

Registry Secret

Kubernetes-кластер для запуска приложения использует образы из registry. Поэтому важно, чтобы кластер мог авторизоваться в registry. Как правило, ситуация отличается для локального и внешнего registry.

Если вы используете локальный registry (как addon в Minikube или поднятый в Docker на основании образа), то авторизация в нём не требуется.

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

Эти логин и пароль мы сообщаем кластеру с помощью объекта типа Secret с именем registrysecret. Допустим, ваш логин — admin, пароль — admin, а registry находится по адресу registry.example.com. Сформируем файл registry-secret.yaml:

.helm/templates/registry-secret.yaml копировать имя копировать текст
apiVersion: v1
kind: Secret
metadata:
  name: registrysecret
  annotations:
    "helm.sh/hook": pre-install
type: kubernetes.io/dockerconfigjson
data:
  {{- $registry := "registry.example.com" }}
  {{- $login := "admin" }}
  {{- $password := "admin" }}
  .dockerconfigjson: {{ printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" $registry (printf "%s:%s" $login $password | b64enc) | b64enc }}
apiVersion: v1 kind: Secret metadata: name: registrysecret annotations: "helm.sh/hook": pre-install type: kubernetes.io/dockerconfigjson data: {{- $registry := "registry.example.com" }} {{- $login := "admin" }} {{- $password := "admin" }} .dockerconfigjson: {{ printf "{\"auths\": {\"%s\": {\"auth\": \"%s\"}}}" $registry (printf "%s:%s" $login $password | b64enc) | b64enc }}

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

Теперь нужно указать этот Secret в каждом объекте Deployment в атрибуте imagePullSecrets, чтобы кластер мог выгрузить образ:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: basicapp
spec:
  selector:
    matchLabels:
      app: basicapp
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
  replicas: 1
  template:
    metadata:
      labels:
        app: basicapp
    spec:
      imagePullSecrets:                           # Added line
      - name: "registrysecret"                    # Added line
      containers:
      - name: basicapp
        command: ["bundle","exec", "puma"]
        image: {{ tuple "basicapp" . | werf_image }}
        workingDir: /app
        ports:
        - containerPort: 3000
          protocol: TCP
        env:
        - name: "SQLITE_FILE"
          value: "app.db"
apiVersion: apps/v1 kind: Deployment metadata: name: basicapp spec: selector: matchLabels: app: basicapp revisionHistoryLimit: 3 strategy: type: RollingUpdate replicas: 1 template: metadata: labels: app: basicapp spec: imagePullSecrets: # Added line - name: "registrysecret" # Added line containers: - name: basicapp command: ["bundle","exec", "puma"] image: {{ tuple "basicapp" . | werf_image }} workingDir: /app ports: - containerPort: 3000 protocol: TCP env: - name: "SQLITE_FILE" value: "app.db"

Service

Объект Service позволяет приложениям в кластере обнаруживать друг друга. Пропишем его:

apiVersion: v1
kind: Service
metadata:
  name: basicapp
spec:
  selector:
    app: basicapp
  ports:
  - name: http
    port: 3000
    protocol: TCP
apiVersion: v1 kind: Service metadata: name: basicapp spec: selector: app: basicapp ports: - name: http port: 3000 protocol: TCP

Ingress

Объект Ingress позволяет организовать маршрутизацию трафика на созданный Service для нужного домена (в нашем примере — example.com).

Что за объект Ingress?

Возможна коллизия терминов:

  • Есть NGINX Ingress Controller, который работает в кластере и принимает входящие извне запросы.
  • А ещё есть объект Ingress, который фактически описывает настройки для NGINX Ingress Controller.

В статьях и бытовой речи оба этих термина зачастую называют «Ingress», так что догадываться нужно по контексту.

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
  name: basicapp
spec:
  rules:
  - host: example.com
    http:
      paths:
      - path: /
        backend:
          serviceName: basicapp
          servicePort: 3000
apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: basicapp spec: rules: - host: example.com http: paths: - path: / backend: serviceName: basicapp servicePort: 3000

Также нужно добавить хост в конфигурацию Rails:

class Application < Rails::Application
  # ...
  config.hosts << 'example.com'
  # ...
end
class Application < Rails::Application # ... config.hosts << 'example.com' # ... end

После этого не забыть зафиксировать изменения!

Выкат в кластер

Перед выполнением выката необходимо добавить изменения в git-репозиторий проекта:

git add .helm/templates
git commit -m "Add helm chart configuration"

Почему изменения должны добавляться в git-репозиторий, что такое гитерминизм и режим разработчика, а также другие особенности работы с файлами проекта будут разобраны далее в главе «Необходимо знать»

Команда werf converge используется для сборки и загрузки образов в registry и последующего выката приложения в Kubernetes. Единственной обязательной опцией указывается репозиторий для хранения образов --repo registry.example.com/werf-guided-springboot.

werf converge --repo registry.example.com/werf-guided-rails
...
│ basicapp/dockerfile  Successfully built 4c1054085159
│ │ basicapp/dockerfile  Successfully tagged 93c05bf8-c459-4768-b388-3cdbc80e2868:latest
│ ├ Info
│ │       name: localhost:5000/werf-guided-rails:f4caaa836701e5346c4a0514bb977362ba5fe4ae114d0176f6a6c8cc-1612277803607
│ │       size: 371.4 MiB
│ └ Building stage basicapp/dockerfile (40.31 seconds)
└ :boat: image basicapp (41.13 seconds)

Release "werf-guided-rails" does not exist. Installing it now.

┌ Waiting for release resources to become ready
│ ┌ Status progress
│ │ DEPLOYMENT                                                                                                                                                      REPLICAS                      AVAILABLE                        UP-TO-DATE
│ │ basicapp                                                                                                                                                        1/1                           1                                1
│ │ │   POD                                                           READY                  RESTARTS                       STATUS
│ │ └── 687f8cc569-n6gkw                                              1/1                    0                              Running
│ └ Status progress
└ Waiting for release resources to become ready (0.02 seconds)

NAME: werf-guided-rails
LAST DEPLOYED: Tue Feb  2 21:57:23 2021
NAMESPACE: werf-guided-rails
STATUS: deployed
REVISION: 1
TEST SUITE: None
Running time 62.66 seconds

После этого приложение должно быть доступно в браузере: