[Docker] [Nginx] Erstellen Sie mit Nginx eine einfache ALB

Ich wollte mehrere Apps zum Entwickeln und Testen starten, deshalb habe ich ein einfaches ALB-Tool erstellt. (Umgebung ist AWS) Ich habe mich aufgrund der folgenden Einschränkungen für ein Tool entschieden.

Zwang:

Da wir Docker verwenden, werden wir als Ansatz die Verwendung des Standardcontainers von nginx in Betracht ziehen. Ursprünglich möchte ich mehrere Apps parallel auf dem Host ausführen, daher werde ich jedem unterschiedliche Ports zuweisen und sie starten und ein Formular anstreben, das mit nginx nach Hostnamen sortiert wird.

simple-alb.png

Da das Verteilungsziel aus Sicht von nginx ein anderer Container ist, der auf demselben Host ausgeführt wird, wird eine Verbindung zum Host innerhalb des Containers hergestellt.

simple-alb-network.png

Da es sich um eine vereinfachte Version als Tool handelt, werden wir fortfahren, solange wir kein neues Docker-Image erstellen oder ein Plug-In hinzufügen.

Ich habe das Tool, das ich erstellt habe, auf GitHub gestellt. https://github.com/batatch/simple-alb

Überblick

Obwohl es sich um eine vereinfachte Version handelt, ähneln die Einstellungen denen von AWS ALB, es wird eine Definitionsdatei erstellt und daraus werden Nginx-Einstellungen generiert → mit dem Ziel einer Konfiguration wie der Docker-Ausführung.

Verfahren:

$ make build    #Generieren Sie eine Nginx-Konfigurationsdatei aus der Definitionsdatei
$ make up       # docker-Weisen Sie die Konfigurationsdatei mit compose zu und starten Sie den Nginx-Container
$ make down     # docker-Stoppen Sie den Nginx-Container mit Compose

Definitionsdatei:

alb.yml


---
http:
  listen: 80                       #Listener, diesmal nur HTTP
  rules:
    - if:                          #IF-Bedingung von ALB
        host: app01.example.com    #Übereinstimmender Hostname
        pathes: [ "/" ]            #Pfadübereinstimmung
      then:                        #ALB DANN Aussage
        forward:                   #Einstellungen übertragen
          name: tg-app01
          targets:                 #Weiterleitungsziel(Mehrere), Bild wie Zielgruppe
            - target: http://docker0:21080
              weight: 30
            - target: http://docker0:22080
          stickiness: true
    :

Die Prozedur ist in einem Makefile zusammengefasst. Ich mag es, aber ich denke, das ist am einfachsten und am einfachsten zu verstehen. Erstellen Sie dann basierend auf der obigen Definitionsdatei die folgende Nginx-Konfigurationsdatei.

nginx/conf.d/default.conf


upstream target1 {
    server http://docker0:21080;
    server http://docker0:22080;
}
server {
    listen 80;
    server_name app01.example.com;
       :
    location / {
        proxy_pass http://target1;
    }
}

Das Framework sieht so aus.

Template Engine

Da die Einstellungsdatei automatisch generiert wird, versuchen Sie es mit Jinja2 von Python, das in Ansible usw. verwendet wird, wenn Sie eine Art Template-Engine möchten.

Ich habe es möglich gemacht, das Konvertierungsergebnis aus der Vorlagendatei und der YAML-Datei der Konfigurationsdatei mit einem einfachen Python-Skript wie dem folgenden abzurufen.

j2.py


import sys
import yaml
from jinja2 import Template, Environment, FileSystemLoader

def _j2(templateFile, configFile):
    env = Environment(loader=FileSystemLoader('.', encoding='utf_8'))
    tpl = env.get_template(templateFile)

    with open(configFile) as f:
        conf = yaml.load(f)

    ret = tpl.render(conf)
    print(ret)

if __name__ == '__main__':
    if (len(sys.argv) <= 2):
        print("Usage: j2.pl <template file> <config file>")
        sys.exit(-1)
    _j2(sys.argv[1], sys.argv[2])

Die Kommandozeile sieht so aus.

$ python j2.pl template.conf.j2 param.yml > output.conf

Kommunikation vom Docker-Container zum Host

Dies ist ziemlich problematisch, und in der Docker-Umgebung von Windows oder Mac scheint es, dass Sie mit host.docker.internal eine Verbindung vom Container zum Host herstellen können, Linux verfügt jedoch nicht über eine solche Methode.

Unter Linux scheint der Host / Container über eine Schnittstelle namens docker0 verbunden zu sein. Daher habe ich die IP-Adresse, die docker0 auf der Hostseite zugewiesen wurde, als Umgebungsvariable festgelegt und beim Start von Docker übergeben.

$ env DOCKER0_ADDRESS=$( ip route | awk '/docker0/ {print $9}' ) \
  docker-compose up -d

docker-compose.yml


version: '3'
services:
  alb:
    image: nginx:stable
      :
    extra_hosts:
      - "docker0:${DOCKER0_ADDRESS}"

Wenn Sie die Zuordnung in extra_hosts in docker-compose.yml schreiben, wird die Zuordnung von Hostname und IP-Adresse zu / etc / hosts im Container hinzugefügt, wenn der Container gestartet wird, sodass Sie anscheinend in den Nginx-Einstellungen auf den Namen verweisen können.

/etc/hosts
----
172.17.0.1      docker0

Road Balancer mit Nginx

Um einen Load Balancer mit nginx einzurichten, definieren Sie eine Gruppe von Zielen in http / upstream und geben Sie den Upstream-Namen in proxy_pass in http / server / location an. .. Ich habe es erwartet, aber es beginnt nicht mit einem Fehler.

Anscheinend erlaubt die kostenlose Version von nginx keinen DNS-Resolver für Hostnamen, die in Upstream geschrieben wurden. (Ich habe zum ersten Mal erfahren, dass Nginx eine kostenpflichtige / kostenlose Version hat.)

Dies ist ein Artikel, der diese Angelegenheit zusammenfasst. Der folgende Qiita-Artikel zeigt, wie UNIX-Sockets verwendet werden. Ich habe mich für dieses entschieden, weil es die Einschränkungen erfüllt, keine Plug-Ins zu verwenden oder Docker-Images zu erstellen. (Obwohl die Einstellungsdatei lang wird)

Zusammenfassung der Nginx-Namensauflösung https://ktrysmt.github.io/blog/name-specification-of-nginx/ Dynamische DNS-Auflösung im Nginx-Upstream-Kontext ohne kostenpflichtige Auflösungsoption https://qiita.com/minamijoyo/items/183e51a28a3a9d79182f

nginx/conf.d/default.conf


upstream tg-app01 {
    server unix:/var/run/nginx_tg-app01_1;  # (2-1) tg-Erstes Ziel von app01
    server unix:/var/run/nginx_tg-app01_2;  # (2-2) tg-Zweites Ziel für app01
}
server {
    listen 80;
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://tg-app01;  # (1) upstream tg-Siehe App01
    }
}
server {
    listen unix:/var/run/nginx_tg-app01_1;  # (2-1)Referenz für das erste Ziel
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://docker0:21080;
    }
}
server {
    listen unix:/var/run/nginx_tg-app01_2;  # (2-2)Referenz für das zweite Ziel
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://docker0:22080;
    }
}

Websocket-Kommunikation

Nehmen Sie die folgenden Einstellungen vor, da diesmal Websocket durchlaufen werden musste. Scheint für jeden Serverblock benötigt zu werden.

nginx/conf.d/default.conf


#Mit diesem
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
    :
server {
    listen 80;
    server_name app02.example.com;

    #von hier
    proxy_set_header Host               $host;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP          $remote_addr;

    proxy_http_version          1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    #Bisher

    location / {
        proxy_pass http://tg-app02;
    }
}

Vorlage für die Nginx-Konfigurationsdatei

Basierend auf dem bisherigen Inhalt lautet die Vorlagendefinition wie folgt. Es ist in Ordnung, aber + α enthält auch die Standardmusterspezifikation und die feste Antworteinstellung.

src/default.conf.j2


## http listener settings
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
{% for rule in http.rules %}
{%- if rule.then.forward %}
upstream {{ rule.then.forward.name }} {
    {%- if rule.then.forward.stickiness %}
    ip_hash;
    {%- endif %}
    {%- for tg in rule.then.forward.targets %}
    server {{ 'unix:/var/run/nginx_%s_%d' % (rule.then.forward.name, loop.index) }}{{ ' weight=%d' % tg.weight if tg.weight else '' }};
    {%- endfor %}
}
{%- endif %}
server {
    listen {{ http.listen }}{{ '  default_server' if rule.if.default_server }};
    server_name {{ rule.if.host }};
    {% if rule.then.forward %}
    proxy_set_header Host               $host;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP          $remote_addr;

    proxy_http_version          1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    {% endif %}
    {%- for path in rule.if.pathes %}
    location {{ path }} {
        {%- if rule.if.headers %}
        {%- for header in rule.if.headers %}
        if ($http_{{ header|replace('-','_')|lower() }} = "{{ rule.if.headers[header] }}") {
            proxy_pass http://{{ rule.then.forward.name }};
            break;
        }
        {%- endfor %}
        {%- else %}
        proxy_pass http://{{ rule.then.forward.name }};
        {%- endif %}
    }
    {%- endfor %}
    {%- if rule.then.response %}
    location / {
       {%- if rule.then.response.content_type %}
       default_type {{ rule.then.response.content_type }};
       {%- endif %}
       return {{ rule.then.response.code }}{{ ' \'%s\'' % rule.then.response.message if rule.then.response.message }};
    }
    {%- endif %}
}
{%- if rule.then.forward %}
{%- for tg in rule.then.forward.targets %}
server {
    listen {{ 'unix:/var/run/nginx_%s_%d' % (rule.then.forward.name, loop.index) }};
    server_name {{ rule.if.host }};
    {% if rule.then.forward %}
    proxy_set_header Host               $host;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host   $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP          $remote_addr;

    proxy_http_version          1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    {% endif %}
    location / {
        proxy_pass {{ tg.target }};
    }
}
{%- endfor %}
{%- endif %}
{% endfor %}

Die Docker-Compose-Einstellungen sind wie folgt. Hängen Sie den Ordner mit den Nginx-Einstellungen auf ein externes Volume, damit die Einstellungen wirksam werden.

docker-compose.yml


version: '3'
services:
  alb:
    image: nginx:stable
    ports:
      - "80:80"
    volumes:
      - ./conf.d:/etc/nginx/conf.d
    extra_hosts:
      - "docker0:${DOCKER0_ADDRESS}"
    restart: always

Die Funktionsprüfung ist wie folgt.

$ make build    #Konvertierung der Einstellungsdatei
$ make up       #Start des Nginx-Containers

$ curl http://localhost:80 -i -H "Host:app01.example.com"  #Zugriff durch Festlegen des Hostnamens
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sat, 29 Aug 2020 17:26:23 GMT
Content-Type: text/html
Content-Length: 1863
Connection: keep-alive
Last-Modified: Wed, 11 Mar 2020 05:22:13 GMT
ETag: "747-5a08d6b34ab40"
Accept-Ranges: bytes

<!DOCTYPE html>
<html>
    :

Zusammenfassung

Ich denke, es wird einfacher zu verwalten sein, da Sie keine AWS ALB-ähnlichen Komponenten in die YAML-Definition schreiben und komplizierte Nginx-Einstellungen schreiben müssen. Sie müssen keine Plugins erstellen oder installieren, sodass Sie sich leicht wie ALB fühlen können, solange Sie das Nginx-Image von DockerHub erhalten.

Ich habe auch gelernt, wie man Nginx einrichtet. (Ich habe auch etwas über die kostenlose / kostenpflichtige Version gelernt.)

Wenn Sie weiter nachforschen und Ihr Bestes geben, können Sie möglicherweise das Einstellungsmuster der ALB-Kopffamilie erkennen. (Dieses Mal wollte ich es so einfach wie möglich machen, also habe ich es nicht gemacht)

Recommended Posts

[Docker] [Nginx] Erstellen Sie mit Nginx eine einfache ALB
Üben Sie das Erstellen einer einfachen Chat-App mit Docker + Sinatra
[Anfänger] Versuchen Sie, mit Java ein einfaches RPG-Spiel zu erstellen ①
Erstellen Sie mit SpringBoot + JPA + Thymeleaf ein einfaches CRUD ~ ~ Hallo Welt ~
Erstellen wir eine einfache API mit EC2 + RDS + Spring Boot ①
Erstellen Sie eine einfache CRUD mit SpringBoot + JPA + Thymeleaf ⑤ ~ Common template ~
Erstellen Sie mit Docker eine Vue3-Umgebung!
Mach eine Sprache! (Einen einfachen Taschenrechner machen ②)
Versuchen Sie, einen einfachen Rückruf zu tätigen
Erstellen Sie mit JavaFX ein Diashow-Tool
Machen Sie eine Müllabfuhr mit line-bot-sdk-java
Erstellen Sie eine Listenkarte mit LazyMap
Mach eine Sprache! (Einen einfachen Taschenrechner machen ①)
Lassen Sie Jupyter Lab überall mit Docker arbeiten
Machen Sie ein Tippspiel mit Ruby
Erstellen Sie mit Docker ein SPA für Laravel 6.2 / Vue.js / Nginx / Mysql / Redis
Erstellen Sie einen C-Compiler zur Verwendung mit Rust x CLion mit Docker
Erstellen Sie mit Docker Compose eine Entwicklungsumgebung für Django + MySQL + nginx
Erstellen Sie mit Sinatra eine Familien-ToDo-Liste
Erstellen Sie mit Dropwizard eine einfache Webanwendung
Erstellen Sie mit Docker eine PureScript-Entwicklungsumgebung
Erstellen Sie mit Spring Batch eine einfache On-Demand-Charge
[Schienenentnahme] Erstellen Sie eine einfache Entnahmefunktion mit Schienen
Erstellen eines einfachen Balkendiagramms mit MPAndroidChart
Erstellen Sie mit Sinatra eine Familien-ToDo-Liste
Erstellen Sie trotzdem eine Anmeldefunktion mit Rails
Erstellen Sie mit Docker eine Wordpress-Entwicklungsumgebung
Implementieren Sie eine einfache CRUD mit Go + MySQL + Docker
Einfache Konstruktion der Docker + Django-Entwicklungsumgebung
[Memo] Erstellen Sie mit Docker ganz einfach eine CentOS 8-Umgebung
Ich habe eine App für maschinelles Lernen mit Dash (+ Docker) Teil 3 ~ Übung ~ erstellt
Erstellen Sie mit Spring Boot eine einfache Such-App
Machen Sie die SpringBoot1.5 + Gradle4.4 + Java8 + Docker-Umgebung mit Java11 kompatibel
Erstellen Sie mit Java + MySQL ein einfaches Bulletin Board
Erstellen Sie eine Laravel / Docker-Umgebung mit VSCode devcontainer
Erstellen Sie mit Docker schnell eine WordPress-Entwicklungsumgebung
Einfache Installation von Nginx und Docker mit ansible
Ich habe versucht, mit Javafx ein einfaches Spiel zu machen ① "Lass uns Glücksspiel finden" (unvollendet)
Einfache Erstellung der Docker Compose + Django-Entwicklungsumgebung
Lassen Sie uns eine Suchfunktion mit Rails (Ransack) machen
Schneller Docker / Nginx
Bereiten Sie eine Scraping-Umgebung mit Docker und Java vor
Erhöhen Sie die Lautstärke, wenn Sie Docker mit vscode verwenden.
Zeigen Sie eine einfache Hallo Welt mit SpringBoot + IntelliJ
Machen Sie System.out Mock mit dem Spock Test Framework
Ein einfaches Stein-Papier-Scheren-Spiel mit JavaFX und SceneBuilder
Erstellen Sie mit Docker eine Spring Boot-Entwicklungsumgebung
Ich habe versucht, ein einfaches Spiel mit Javafx zu machen ① "Lass uns Glücksspiel finden" (unvollendete Version ②)