Nach monatelangen Tests haben wir die Ruby on Rails-Anwendung schließlich mit dem Kubernetes-Cluster auf die Produktion portiert.
In diesem Artikel werde ich Ihnen zeigen, wie Sie das pfadbasierte Routing für eine Ruby on Rails-Anwendung in Kubernetes mit einem HAProxy Ingress-Controller konfigurieren.

Es wird davon ausgegangen, dass Sie eine Vorstellung davon haben, was Pods , Bereitstellungen , Dienste , eine Konfigurationszuordnung und Ingress in Kubernetes sind.
Normalerweise gibt es in einer Rails-Anwendung Dienste wie Unicorn / Puma, Sidekiq / Delayed Job / Resque, Web Sockets und mehrere spezielle API-Dienste. Wir hatten einen Webdienst über den Balancer geöffnet, und alles hat gut funktioniert. Aber der Verkehr wuchs und es war notwendig, ihn per URL oder Pfad weiterzuleiten.
Kubernetes hat keine schlüsselfertige Lösung, um diese Art von Last auszugleichen. Ein Alb-Ingress-Controller wird bereits dafür entwickelt, befindet sich jedoch noch nicht im Alpha-Stadium und für die Produktion.
Für das pfadbasierte Routing ist es am besten, einen Ingress-Controller zu verwenden .
Wir haben das Problem untersucht und festgestellt, dass k8s unterschiedliche Lösungen für Ingress bietet.
Wir haben mit Nginx-Ingress und HAProxy experimentiert und uns für HAProxy entschieden - es ist besser für die Rails-Web-Sockets, die wir im Projekt verwendet haben.
Ich zeige Ihnen Schritt für Schritt, wie Sie HAProxy Ingress an eine Rails-Anwendung anhängen.
Einrichten einer Rails-Anwendung mit einem HAProxy Ingress-Controller
Folgendes werden wir tun:
- Erstellen wir eine Rails-Anwendung mit verschiedenen Diensten und Bereitstellungen.
- Erstellen Sie ein TLS-Geheimnis für SSL.
- Erstellen Sie eine HAProxy Ingress-Konfigurationszuordnung.
- Erstellen Sie einen HAProxy Ingress-Controller.
- Lassen Sie uns den Zugriff auf Ingress über einen Dienst wie LoadBalancer öffnen.
- Lassen Sie uns DNS-Anwendungen für den Ingress-Dienst konfigurieren.
- Erstellen Sie verschiedene Ingress-Regeln für das pfadbasierte Routing.
- Testen Sie das Routing basierend auf dem Pfad.
Erstellen wir ein Manifest für die Bereitstellung einer Rails-Anwendung für verschiedene Dienste - Web (Einhorn), Hintergrundaufgaben (Sidekiq), Web-Socket (Ruby Thin), API (Einhorn dediziert).
Hier ist unsere Vorlage für die Bereitstellung von Webanwendungen und Diensten.
--- 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
Hier ist die Hintergrundvorlage für die Bereitstellung und den Dienst der App.
--- 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
Hier finden Sie den Socket und die Dienstvorlage für die Bereitstellung von Webanwendungen.
--- 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
Hier finden Sie Informationen zur Bereitstellung und zum Dienst der Anwendungs-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
Lassen Sie uns das Manifest mit dem 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
Sobald die Anwendung bereitgestellt und gestartet wurde, müssen Sie einen HAProxy Ingress erstellen. Aber zuerst erstellen wir ein TLS-Geheimnis mit einem SSL-Schlüssel und einem Zertifikat.
Er lässt HTTPS für die Anwendungs-URL zu und beendet sie auf L7.
$ kubectl create secret tls tls-certificate --key server.key --cert server.pem
server.key
hier ist unser SSL-Schlüssel und server.pem
ist unser SSL-Zertifikat im PEM-Format.
Erstellen Sie nun die Ressourcen des HAProxy-Controllers.
HAProxy-Konfigurationskarte
Alle verfügbaren Konfigurationsoptionen für HAProxy finden Sie hier .
apiVersion: v1 data: dynamic-scaling: "true" backend-server-slots-increment: "4" kind: ConfigMap metadata: name: haproxy-configmap namespace: test
HAProxy Ingress Controller-Bereitstellung
Bereitstellungsmuster für einen Ingress-Controller mit mindestens zwei Replikaten zum Verwalten einer sequentiellen Bereitstellung.
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
In diesem Manifest interessieren uns insbesondere die Argumente, die an den Controller übergeben werden.
--default-backend-service
ist der Service, den die Anwendung verwendet, wenn keine Regeln mit der Anforderung übereinstimmen.
Wir haben diesen test-production-web
, aber es kann sich um eine benutzerdefinierte 404-Seite oder ähnliches handeln - Sie entscheiden.
--default-ssl-certificate
ist das gerade erstellte SSL-Geheimnis. Es wird SSL auf L7 beenden und die Anwendung wird extern über HTTPS zugänglich sein.
HAProxy Ingress Service
Dies ist eine Art LoadBalancer
Dienst, der den Zugriff des LoadBalancer
auf unseren Ingress-Controller ermöglicht.
Der LoadBalancer hat Zugriff auf das öffentliche Netzwerk und das interne Netzwerk von Kubernetes und leitet auf L7 den Datenverkehr an den Ingress-Controller weiter.
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
Wenden wir alle HAProxy-Manifeste an.
$ 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
Wenn alle Ressourcen ausgeführt werden, geben Sie den Endpunkt des LoadBalancer an.
$ 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
Ordnen Sie DNS der Anwendungs-URL zu
Sobald wir den ELB-Endpunkt für den Ingress-Dienst angeben, müssen wir den DNS des Dienstes und die Anforderungs-URL (z. B. test-rails-app.com
) test-rails-app.com
.
Ingress-Implementierung
Der schwierigste Teil liegt dahinter, es ist Zeit, Ingress- und Path-basierte Regeln zu konfigurieren.
Wir brauchen die folgenden Regeln.
Anfragen an https://test-rails-app.com werden vom test-production-web
bearbeitet.
Anfragen an https://test-rails-app.com/websocket werden vom test-production-websocket
bearbeitet.
Anfragen an https://test-rails-app.com/api werden vom test-production-api
bearbeitet.
Erstellen wir ein Ingress-Manifest mit all diesen Regeln.
--- 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
Im Falle von Konfigurationsänderungen haben wir Anmerkungen für Ingress-Ressourcen .
Wie erwartet wird unser Datenverkehr standardmäßig an /
an den test-production-web
, /api
an test-production-api
und /websocket
an test-production-websocket
.
Wir brauchten Pfad-basiertes Routing und SSL-Terminierung auf L7 in Kubernetes, und die Ingress-Implementierung löste dieses Problem.