[docker] [nginx] Créer un ALB simple avec nginx

Je voulais lancer plusieurs applications pour le développement et les tests, j'ai donc créé un outil ALB simple. (L'environnement est AWS) J'ai décidé de créer un outil en raison des restrictions suivantes.

Contrainte:

Comme approche, puisque nous utilisons docker, nous envisagerons d'utiliser le conteneur par défaut de nginx. À l'origine, je souhaite exécuter plusieurs applications en parallèle sur l'hôte, je vais donc allouer différents ports pour chacun et les démarrer, et viser un formulaire qui trie par nom d'hôte avec nginx.

simple-alb.png

Étant donné que la destination de distribution est un autre conteneur fonctionnant sur le même hôte, du point de vue de nginx, il se connectera à l'hôte depuis le conteneur.

simple-alb-network.png

Puisqu'il s'agit d'une version simplifiée en tant qu'outil, nous continuerons tant que nous ne créerons pas de nouvelle image de menu fixe ou n'ajoutons pas de plug-in.

J'ai mis l'outil que j'ai créé sur GitHub. https://github.com/batatch/simple-alb

Aperçu

Bien qu'il s'agisse d'une version simplifiée, les paramètres sont similaires à ceux d'AWS ALB, un fichier de définition est préparé et les paramètres nginx sont générés à partir de celui-ci → visant une configuration telle que l'exécution du docker.

procédure:

$ make build    #Générer le fichier de configuration nginx à partir du fichier de définition
$ make up       # docker-Attribuez le fichier de configuration avec compose et démarrez le conteneur nginx
$ make down     # docker-Arrêter le conteneur nginx avec compose

Fichier de définition:

alb.yml


---
http:
  listen: 80                       #Auditeur, cette fois HTTP uniquement
  rules:
    - if:                          #SI condition d'ALB
        host: app01.example.com    #Correspondance du nom d'hôte
        pathes: [ "/" ]            #Correspondance de chemin
      then:                        #Déclaration ALB THEN
        forward:                   #Paramètres de transfert
          name: tg-app01
          targets:                 #Destination de transfert(Plusieurs), Image comme groupe cible
            - target: http://docker0:21080
              weight: 30
            - target: http://docker0:22080
          stickiness: true
    :

La procédure est résumée dans un Makefile. J'aime ça, mais je pense que c'est le plus simple et le plus facile à comprendre. Ensuite, en vous basant sur le fichier de définition ci-dessus, créez le fichier de configuration nginx suivant.

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

Le cadre ressemble à ceci.

Moteur de modèle

Étant donné que le fichier de paramètres est généré automatiquement, si vous voulez une sorte de moteur de modèle, essayez d'utiliser Jinja2 de Python utilisé dans Ansible, etc.

J'ai rendu possible l'obtention du résultat de la conversion à partir du fichier modèle et du fichier YAML du fichier de configuration avec un simple script Python comme celui-ci.

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

La ligne de commande ressemble à ceci.

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

Communication du conteneur docker à l'hôte

C'est assez gênant, et dans l'environnement Docker de Windows ou Mac, il semble que vous puissiez vous connecter de l'intérieur du conteneur à l'hôte avec host.docker.internal, mais Linux n'a pas une telle méthode.

Sous Linux, il semble que l'hôte / conteneur soit connecté par une interface appelée docker0, j'ai donc attribué l'adresse IP à docker0 du côté hôte, en ai fait une variable d'environnement et l'ai transmise au démarrage de docker.

$ 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}"

Si vous écrivez le mappage dans extra_hosts dans docker-compose.yml, le mappage du nom d'hôte et de l'adresse IP sera ajouté à / etc / hosts dans le conteneur lorsque le conteneur est démarré, il semble donc que vous puissiez vous référer au nom dans les paramètres nginx.

/etc/hosts
----
172.17.0.1      docker0

Équilibreuse de route avec Nginx

Pour configurer un équilibreur de charge avec nginx, définissez un groupe de cibles dans http / upstream et spécifiez le nom en amont dans proxy_pass dans http / server / location. .. Je m'y attendais, mais cela ne commence pas par une erreur.

Apparemment, la version gratuite de nginx n'autorise pas le résolveur DNS pour les noms d'hôtes écrits en amont. (J'ai appris pour la première fois que nginx avait une version payante / gratuite.)

Ceci est un article résumant cette question. L'article Qiita ci-dessous montre comment utiliser les sockets UNIX. J'ai choisi celui-ci car il respectait les restrictions de ne pas utiliser de plug-ins ou de créer des images docker. (Bien que le fichier de paramètres devienne long)

Résumé de la résolution de nom Nginx https://ktrysmt.github.io/blog/name-specification-of-nginx/ Résolution DNS dynamique dans le contexte amont Nginx sans option de résolution payante 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-Première cible de l'app01
    server unix:/var/run/nginx_tg-app01_2;  # (2-2) tg-Deuxième cible pour app01
}
server {
    listen 80;
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://tg-app01;  # (1) upstream tg-Voir app01
    }
}
server {
    listen unix:/var/run/nginx_tg-app01_1;  # (2-1)Référence pour la première cible
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://docker0:21080;
    }
}
server {
    listen unix:/var/run/nginx_tg-app01_2;  # (2-2)Référence pour la deuxième cible
    server_name app01.example.com;
        :
    location / {
        proxy_pass http://docker0:22080;
    }
}

Communication Websocket

Puisqu'il était nécessaire de passer par Websocket cette fois, effectuez les réglages suivants. Semble être nécessaire pour chaque bloc de serveur.

nginx/conf.d/default.conf


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

    #d'ici
    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;
    #Jusque là

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

modèle de fichier de configuration nginx

Sur la base du contenu jusqu'à présent, la définition du modèle est la suivante. C'est bien, mais + α inclut également la spécification de modèle par défaut et le paramètre de réponse fixe.

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

Les paramètres de composition du menu fixe sont les suivants. Montez le dossier de paramètres nginx sur un volume externe pour que les paramètres prennent effet.

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

Le contrôle de fonctionnement est le suivant.

$ make build    #Conversion de fichiers de paramètres
$ make up       #démarrage du conteneur nginx

$ curl http://localhost:80 -i -H "Host:app01.example.com"  #Accès en définissant le nom d'hôte
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>
    :

Sommaire

Je pense que ce sera plus facile à gérer car vous n'avez pas besoin d'écrire des composants de type AWS ALB dans la définition YAML et d'écrire des paramètres nginx compliqués. Vous n'avez pas besoin de créer ou d'installer de plug-ins, il est donc facile de se sentir comme ALB tant que vous obtenez l'image nginx de DockerHub.

J'ai également appris à configurer nginx. (J'ai également découvert la version gratuite / payante.)

Si vous approfondissez vos recherches et faites de votre mieux, vous pourrez peut-être vous rendre compte du modèle de configuration de la famille principale ALB. (Cette fois, je voulais le rendre aussi facile à utiliser que possible, donc je ne l'ai pas fait)

Recommended Posts

[docker] [nginx] Créer un ALB simple avec nginx
Entraînez-vous à créer une application de chat simple avec Docker + Sinatra
[Débutant] Essayez de créer un jeu RPG simple avec Java ①
Créez un CRUD simple avec SpringBoot + JPA + Thymeleaf ① ~ Hello World ~
Faisons une API simple avec EC2 + RDS + Spring boot ①
Créez un CRUD simple avec SpringBoot + JPA + Thymeleaf ⑤ ~ Modèle commun ~
Créez un environnement Vue3 avec Docker!
Faites une langue! (Faire une simple calculatrice ②)
Essayez de faire un simple rappel
Créer un outil de diaporama avec JavaFX
Faire un rappel de garbage avec line-bot-sdk-java
Créer une carte de liste avec LazyMap
Faites une langue! (Faire une simple calculatrice ①)
Faites fonctionner Jupyter Lab n'importe où avec Docker
Faites un jeu de frappe avec ruby
Construire un SPA pour Laravel 6.2 / Vue.js / Nginx / Mysql / Redis avec Docker
Créer un compilateur C à utiliser avec Rust x CLion avec Docker
Créer un environnement de développement pour Django + MySQL + nginx avec Docker Compose
Faites une liste de choses à faire en famille avec Sinatra
Créez une application Web simple avec Dropwizard
Créer un environnement de développement PureScript avec Docker
Créez un lot à la demande simple avec Spring Batch
[Retrait des rails] Créez une fonction de retrait simple avec des rails
Créer un graphique à barres simple avec MPAndroidChart
Faites une liste de choses à faire en famille avec Sinatra
Créez quand même une fonction de connexion avec Rails
Créer un environnement de développement Wordpress avec Docker
Implémentez un CRUD simple avec Go + MySQL + Docker
Construction d'un environnement de développement simple Docker + Django
[Memo] Créez facilement un environnement CentOS 8 avec Docker
J'ai créé une application d'apprentissage automatique avec Dash (+ Docker) part3 ~ Practice ~
Créez une application de recherche simple avec Spring Boot
Rendre l'environnement SpringBoot1.5 + Gradle4.4 + Java8 + Docker compatible avec Java11
Créez un tableau d'affichage simple avec Java + MySQL
Créer un environnement Laravel / Docker avec VSCode devcontainer
Créez rapidement un environnement de développement WordPress avec Docker
Installation simple de nginx et Docker à l'aide d'ansible
J'ai essayé de faire un jeu simple avec Javafx ① "Trouvons le jeu du bonheur" (inachevé)
Construction de l'environnement de développement Simple Docker Compose + Django
Faisons une fonction de recherche avec Rails (ransack)
Docker rapide / nginx
Préparer un environnement de scraping avec Docker et Java
Accélérez le volume en utilisant Docker avec vscode.
Afficher un simple Hello World avec SpringBoot + IntelliJ
Rendre System.out Mock avec Spock Test Framework
Un simple jeu de ciseaux-papier-pierre avec JavaFX et SceneBuilder
Créer un environnement de développement Spring Boot avec docker
J'ai essayé de faire un jeu simple avec Javafx ① "Trouvons le jeu du bonheur" (version inachevée ②)