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.
É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.
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
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.
É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
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
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;
}
}
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;
}
}
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>
:
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