- PVSM.RU - https://www.pvsm.ru -

Как развернуть приложение Ruby on Rails с HAProxy Ingress, unicorn-puma и веб-сокетами

После нескольких месяцев тестов мы наконец перенесли приложение Ruby on Rails в продакшен с кластером Kubernetes.

В этой статье я расскажу, как настроить маршрутизацию на основе Path для приложения Ruby on Rails в Kubernetes с контроллером HAProxy Ingress.

image

Предполагается, что вы примерно представляете себе, что такое поды [1], деплои [2], сервисы [3], карта конфигурации [4] и Ingress [5] в Kubernetes [6]

Обычно в Rails-приложении есть такие сервисы, как unicorn/puma, sidekiq/delayed-job/resque, веб-сокеты и несколько специальных API сервисов. У нас был один веб-сервис, открытый наружу через балансировщик, и все работало нормально. Но трафик рос, и нужно было маршрутизировать его по URL или Path.

В Kubernetes нет готового решения для балансировки нагрузки такого типа. Под нее уже разрабатывается alb-ingress-controller [7], но он пока на стадии альфы и для продакшена не подходит.

Для маршрутизации на основе Path лучше всего было использовать Ingress-контроллер [8].

Мы изучили вопрос и узнали, что в k8s есть разные решения для Ingress.

Мы поэкспериментировали с nginx-ingress и HAProxy и остановились на HAProxy — он лучше подходит для веб-сокетов Rails, которые мы использовали в проекте.

Я расскажу пошагово, как прикрутить HAProxy Ingress к Rails-приложению.

Настройка Rails-приложения с контроллером HAProxy Ingress

Вот что мы будем делать:

  • Создадим Rails-приложение с разными сервисами и деплоями.
  • Создадим секрет TLS для SSL.
  • Создадим карту конфигурации HAProxy Ingress.
  • Создадим контроллер HAProxy Ingress.
  • Откроем доступ к Ingress через сервис типа LoadBalancer.
  • Настроим DNS приложения для сервиса Ingress.
  • Создадим разные правила Ingress для маршрутизации на основе Path.
  • Протестируем маршрутизацию на основе Path.

Давайте создадим манифест развертывания Rails-приложения для разных сервисов — веб (unicorn), фоновые задачи (sidekiq), веб-сокет (ruby thin), API (выделенный unicorn).

Вот наш деплой веб-приложения и шаблон сервиса.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-web
  labels:
    app: test-production-web
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-web
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: web
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-web
  labels:
    app: test-production-web
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-web

Вот деплой фонового приложения и шаблон сервиса.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-background
  labels:
    app: test-production-background
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-background
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: background
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-background
  labels:
    app: test-production-background
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-background

Вот деплой веб-сокет приложения и шаблон сервиса.

---
apiVersion: v1
kind: Deployment
metadata:
  name: test-production-websocket
  labels:
    app: test-production-websocket
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-websocket
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: websocket
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-websocket
  labels:
    app: test-production-websocket
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-websocket

Вот деплой API приложения и сведения о сервисе.

---
`apiVersion: v1
kind: Deployment
metadata:
  name: test-production-api
  labels:
    app: test-production-api
  namespace: test
spec:
  template:
    metadata:
      labels:
        app: test-production-api
    spec:
      containers:
      - image: <your-repo>/<your-image-name>:latest
        name: test-production
        imagePullPolicy: Always
       env:
        - name: POSTGRES_HOST
          value: test-production-postgres
        - name: REDIS_HOST
          value: test-production-redis
        - name: APP_ENV
          value: production
        - name: APP_TYPE
          value: api
        - name: CLIENT
          value: test
        ports:
        - containerPort: 80
      imagePullSecrets:
        - name: registrykey
---
apiVersion: v1
kind: Service
metadata:
  name: test-production-api
  labels:
    app: test-production-api
  namespace: test
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: test-production-api

Давайте запустим манифест командой kubectl apply.

$ kubectl apply -f test-web.yml -f test-background.yml -f test-websocket.yml -f test-api.yml
deployment "test-production-web" created
service "test-production-web" created
deployment "test-production-background" created
service "test-production-background" created
deployment "test-production-websocket" created
service "test-production-websocket" created
deployment "test-production-api" created
service "test-production-api" created

Как только приложение будет развернуто и запущено, нужно будет создать HAProxy Ingress. Но сначала давайте создадим секрет TLS с ключом и сертификатом SSL.

Он же будет разрешать HTTPS для URL приложения и терминировать его на L7.

$ kubectl create secret tls tls-certificate --key server.key --cert server.pem

server.key здесь — это наш ключ SSL, а server.pem — наш сертификат SSL в формате pem.

Теперь создадим ресурсы контроллера HAProxy.

Карта конфигурации HAProxy

Все доступные параметры конфигурации для HAProxy смотрите здесь [14].

apiVersion: v1
data:
    dynamic-scaling: "true"
    backend-server-slots-increment: "4"
kind: ConfigMap
metadata:
  name: haproxy-configmap
  namespace: test

Развертывание контроллера HAProxy Ingress

Шаблон развертывания для Ingress-контроллера минимум с двумя репликами для управления последовательным деплоем.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      run: haproxy-ingress
  template:
    metadata:
      labels:
        run: haproxy-ingress
    spec:
      containers:
      - name: haproxy-ingress
        image: quay.io/jcmoraisjr/haproxy-ingress:v0.5-beta.1
        args:
        - --default-backend-service=$(POD_NAMESPACE)/test-production-web
        - --default-ssl-certificate=$(POD_NAMESPACE)/tls-certificate
        - --configmap=$(POD_NAMESPACE)/haproxy-configmap
        - --ingress-class=haproxy
        ports:
        - name: http
          containerPort: 80
        - name: https
          containerPort: 443
        - name: stat
          containerPort: 1936
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace

В этом манифесте нас особенно интересуют аргументы, передаваемые контроллеру.
--default-backend-service — это сервис, который приложение будет использовать, если запросу не соответствуют никакие правила.

У нас это сервис test-production-web, но это может быть кастомная страница 404 или что-нибудь подобное — решать вам.

--default-ssl-certificate — это секрет SSL, который мы только что создали. Он будет терминировать SSL на L7, и приложение будет доступно извне по HTTPS.

Сервис HAProxy Ingress

Это тип сервиса LoadBalancer, который разрешает клиентскому трафику доступ к нашему Ingress-контроллеру.

У LoadBalancer есть доступ к публичной сети и внутренней сети Kubernetes, а на L7 он маршрутизирует трафик для Ingress-контроллера.

apiVersion: v1
kind: Service
metadata:
  labels:
    run: haproxy-ingress
  name: haproxy-ingress
  namespace: test
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
  - name: https
    port: 443
    protocol: TCP
    targetPort: 443
  - name: stat
    port: 1936
    protocol: TCP
    targetPort: 1936
  selector:
    run: haproxy-ingress

Давайте применим все манифесты HAProxy.

$ kubectl apply -f haproxy-configmap.yml -f haproxy-deployment.yml -f haproxy-service.yml
configmap "haproxy-configmap" created
deployment "haproxy-ingress" created
service "haproxy-ingress" created

Когда все ресурсы будут запущены, укажите конечную точку LoadBalancer.

$ kubectl -n test get svc haproxy-ingress -o wide

NAME               TYPE           CLUSTER-IP       EXTERNAL-IP                                                            PORT(S)                                     AGE       SELECTOR
haproxy-ingress   LoadBalancer   100.67.194.186   a694abcdefghi11e8bc3b0af2eb5c5d8-806901662.us-east-1.elb.amazonaws.com   80:31788/TCP,443:32274/TCP,1936:32157/TCP   2m        run=ingress

Сопоставление DNS с URL приложения

Как только мы укажем конечную точку ELB для сервиса Ingress, нужно будет сопоставить между собой DNS сервиса и URL запроса (например test-rails-app.com).

Реализация Ingress

Самое сложное позади, пора настроить Ingress и правила на основе Path.

Нам нужны нужны следующие правила.

Запросы к https://test-rails-app.com [15] будут обрабатываться сервисом test-production-web.

Запросы к https://test-rails-app.com/websocket [16] будут обрабатываться сервисом test-production-websocket.

Запросы к https://test-rails-app.com/api [17] будут обрабатываться сервисом test-production-api.

Давайте создадим манифест Ingress со всеми этими правилами.

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress
  namespace: test
spec:
  tls:
    - hosts:
      - test-rails-app.com
      secretName: tls-certificate
  rules:
    - host: test-rails-app.com
      http:
        paths:
          - path: /
            backend:
              serviceName: test-production-web
              servicePort: 80
          - path: /api
            backend:
              serviceName: test-production-api
              servicePort: 80
          - path: /websocket
            backend:
              serviceName: test-production-websocket
              servicePort: 80

На случай изменений конфигурации у нас есть аннотации для Ingress ресурсов [18].

Как и ожидалось, по умолчанию наш трафик на / маршрутизируется в сервис test-production-web, /api — в test-production-api, а /websocket — в test-production-websocket.

Нам нужна была маршрутизация на основе Path и терминация SSL на L7 в Kubernetes, и реализация Ingress решила эту задачу.

Автор: nAbdullin

Источник [19]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby-on-rails/294302

Ссылки в тексте:

[1] поды: https://kubernetes.io/docs/concepts/workloads/pods/pod/

[2] деплои: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/

[3] сервисы: https://kubernetes.io/docs/concepts/services-networking/service/

[4] карта конфигурации: https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/

[5] Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/

[6] Kubernetes: https://kubernetes.io

[7] alb-ingress-controller: https://github.com/kubernetes-sigs/aws-alb-ingress-controller

[8] Ingress-контроллер: https://kubernetes.io/docs/concepts/services-networking/ingress/#ingress-controllers

[9] nginx-ingress: https://github.com/kubernetes/ingress-nginx

[10] ingress-gce: https://github.com/kubernetes/ingress-gce

[11] HAProxy-ingress: https://github.com/jcmoraisjr/haproxy-ingress

[12] traefik: https://docs.traefik.io/configuration/backends/kubernetes/

[13] voyager: https://github.com/appscode/voyager

[14] здесь: https://github.com/jcmoraisjr/HAProxy-ingress#configmap

[15] https://test-rails-app.com: https://test-rails-app.com

[16] https://test-rails-app.com/websocket: https://test-rails-app.com/websocket

[17] https://test-rails-app.com/api: https://test-rails-app.com/api

[18] аннотации для Ingress ресурсов: https://github.com/jcmoraisjr/haproxy-ingress#annotations

[19] Источник: https://habr.com/post/424789/?utm_campaign=424789