Comment déployer une application Ruby on Rails avec HAProxy Ingress, unicorn / puma et des sockets Web

Après des mois de tests, nous avons finalement porté l'application Ruby on Rails en production avec le cluster Kubernetes.


Dans cet article, je vais vous montrer comment configurer le routage basé sur le chemin pour une application Ruby on Rails dans Kubernetes avec un contrôleur HAProxy Ingress.


image


On suppose que vous avez une idée de ce que sont les pods , les déploiements , les services , une carte de configuration et Ingress dans Kubernetes.


Habituellement, dans une application Rails, il existe des services tels que licorne / puma, sidekiq / travail retardé / resque, sockets Web et plusieurs services API spéciaux. Nous avions un service Web ouvert via l'équilibreur, et tout fonctionnait bien. Mais le trafic augmentait et il fallait le router par URL ou par chemin.


Kubernetes ne dispose pas de solution clé en main pour équilibrer ce type de charge. Un contrôleur d'alb-ingress est déjà en cours de développement pour lui, mais il n'est toujours pas au stade alpha et pour la production.


Pour le routage basé sur le chemin, il était préférable d'utiliser un contrôleur Ingress .


Nous avons étudié le problème et découvert que k8s propose différentes solutions pour Ingress.



Nous avons expérimenté avec nginx-ingress et HAProxy et opté pour HAProxy - il est mieux adapté aux sockets Web Rails que nous avons utilisés dans le projet.


Je vais vous montrer étape par étape comment attacher HAProxy Ingress à une application Rails.


Configuration d'une application Rails avec un contrôleur HAProxy Ingress


Voici ce que nous allons faire:


  • Créons une application Rails avec différents services et déploiements.
  • Créez un secret TLS pour SSL.
  • Créez une carte de configuration HAProxy Ingress.
  • Créez un contrôleur d'entrée HAProxy.
  • Ouvrons l'accès à Ingress via un service comme LoadBalancer.
  • Configurons les applications DNS pour le service Ingress.
  • Créez différentes règles d'entrée pour le routage basé sur le chemin.
  • Tester le routage en fonction du chemin.

Créons un manifeste pour déployer une application Rails pour différents services - web (licorne), tâches en arrière-plan (sidekiq), socket web (ruby thin), API (dédié à la licorne).


Voici notre modèle d'application et de service de déploiement Web.


--- 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 

Voici le modèle de déploiement et de service d'application en arrière-plan.


 --- 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 

Voici le socket de déploiement d'application Web et le modèle de service.


 --- 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 

Voici les informations de déploiement et de service de l'API d'application.


 --- `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 le manifeste avec la 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 

Une fois l'application déployée et lancée, vous devrez créer une entrée HAProxy. Mais d'abord, créons un secret TLS avec une clé SSL et un certificat.


Il autorisera HTTPS pour l'URL de l'application et la terminera sur L7.


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


server.key est ici notre clé SSL, et server.pem est notre certificat SSL au format pem.


Créez maintenant les ressources du contrôleur HAProxy.


Carte de configuration HAProxy


Voir toutes les options de configuration disponibles pour HAProxy ici .


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

Déploiement du contrôleur d'entrée HAProxy


Modèle de déploiement pour un contrôleur Ingress avec au moins deux répliques pour gérer un déploiement séquentiel.


 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 

Dans ce manifeste, nous sommes particulièrement intéressés par les arguments transmis au contrôleur.
--default-backend-service est le service que l'application utilisera si aucune règle ne correspond à la demande.


Nous avons ce service de test-production-web , mais il peut s'agir d'une page 404 personnalisée ou quelque chose comme ça - vous décidez.


--default-ssl-certificate est le secret SSL que nous venons de créer. Il mettra fin à SSL sur L7 et l'application sera accessible en externe via HTTPS.


Service d'entrée HAProxy


Il s'agit d'un type de service LoadBalancer qui permet au trafic client d'accéder à notre contrôleur Ingress.


Le LoadBalancer a accès au réseau public et au réseau interne de Kubernetes et, sur L7, il achemine le trafic vers le contrôleur 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 

Appliquons tous les manifestes 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 

Lorsque toutes les ressources sont en cours d'exécution, spécifiez le point de terminaison de 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 

Mapper DNS à l'URL de l'application


Dès que nous spécifierons le point de terminaison ELB pour le service Ingress, nous devrons mapper le DNS du service et l'URL de la demande (par exemple test-rails-app.com ).


Implémentation d'entrée


La partie la plus difficile est derrière, il est temps de configurer les règles basées sur Ingress et Path.


Nous avons besoin des règles suivantes.


Les demandes adressées à https://test-rails-app.com seront traitées par le service test-production-web .


Les demandes à https://test-rails-app.com/websocket seront traitées par le service test-production-websocket .


Les demandes à https://test-rails-app.com/api seront traitées par le service test-production-api .


Créons un manifeste Ingress avec toutes ces règles.


 --- 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 

En cas de changements de configuration, nous avons des annotations pour les ressources Ingress .


Comme prévu, par défaut, notre trafic vers / acheminé vers le service test-production-web , /api vers test-production-api et /websocket vers test-production-websocket .


Nous avions besoin d'un routage basé sur le chemin et d'une terminaison SSL sur L7 dans Kubernetes, et l'implémentation Ingress a résolu ce problème.

Source: https://habr.com/ru/post/fr424789/


All Articles