Templating NGiNX Configs with Ansible - Thu, Oct 22, 2020
Templating NGiNX Configs with Ansible
Today I started down the path of trying to template out the Nginx load balancer configs for our systems. I wanted to find out how others were building their templates out of yaml configs. There were not very many documents on it, so I had to figure it out myself.
Lets see what a basic config file in YAML format would look like in this example.
filename: test123.conf
template: generic_site_template.j2
upstream:
name: cms
servers:
- webserver1.example.local:
weight: 10
- webserver2.example.local:
fail_timeout: 2s
weight: 12
- webserver3.example.local:
backup:
port: 443
options:
zonesize: 256k
sticky: sticky cookie srv_id expires=1h domain=.example.local path=/
balancing: least_conn
keepalive: 32
ntlm:
queue:
number: 100
timeout: 120
server:
ssl:
ciphers: "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA HIGH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS"
cache: "shared:SSL:20m"
timeout: 24h
enabled: true
ssl_certificate: /etc/ssl/www/www.example.local.crt
ssl_certificate_key: /etc/ssl/www/www.example.local.key
server_name: www.example.local
logging:
access: /var/log/nginx/www_access.log
error: /var/log/nginx/www_error.log
syslog: 172.20.20.20
health_check: health_check match=server_ok passes=2
options:
gzip:
max_body_size: 20M
keepalive:
requests: 10000
timeout: 300s
proxy:
tcp:
sni:
proxy_host: www.example.local
headers:
connection:
client:
timeouts:
connect: 300s
send: 300s
read: 300s
When I import this as include_vars setting the name as nginx_template, I can then run a it through the template I made.
{# ################# Upstream Block ################# #}
{% if 'upstream' in nginx_template %}
upstream {{ nginx_template.upstream.name }} {
zone {{ nginx_template.upstream.name }} {{ nginx_template.upstream.options.zonesize | default('512k', true) }};
{{ nginx_template.upstream.options.balancing | default('least_conn', true) }};
{% for server in nginx_template.upstream.servers %}
{% for key, value in server.items() %}
server {{ key }}:{{ nginx_template.upstream.port }}{% if value != None and 'weight' in value %} weight={{ value.weight }}{% endif %}{% if value != None and 'fail_timeout' in value %} fail_timeout={{ value.fail_timeout }}{% endif %}{% if value != None and 'backup' in value %} backup{% endif %};
{% endfor %}
{% endfor %}
{% if 'sticky' in nginx_template.upstream.options %}
{{ nginx_template.upstream.options.sticky }};
{% endif %}
{% if 'keepalive' in nginx_template.upstream.options %}
keepalive {{ nginx_template.upstream.options.keepalive }};
{% endif %}
{% if 'queue' in nginx_template.upstream.options %}
queue {{ nginx_template.upstream.options.queue.number | default("100") }} timeout={{ nginx_template.upstream.options.queue.timeout | default(120) }};
{% endif %}
{% if 'ntlm' in nginx_template.upstream.options %}
ntlm;
{% endif %}
}
{% endif %}
{# ################# Server with SSL ################# #}
{% if 'ssl' in nginx_template.server %}
server {
listen 80;
server_name {{ nginx_template.server.server_name | default('default', true) }};
return 301 https://$server_name$request_uri;
}
server {
listen {{ nginx_template.server.port | default('443') }} ssl;
server_name {{ nginx_template.server.server_name | default('default', true) }};
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "{{ nginx_template.server.ssl.ciphers | default ('EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA HIGH !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS') }}";
ssl_session_cache {{ nginx_template.server.ssl.cache | default("shared:SSL:10m") }};
ssl_session_timeout {{ nginx_template.server.ssl.timeout | default("24h") }};
ssl_certificate {{ nginx_template.server.ssl.ssl_certificate }};
ssl_certificate_key {{ nginx_template.server.ssl.ssl_certificate_key }};
{% endif %}
{# ################# Server without SSL ################# #}
{% if 'ssl' not in nginx_template.server %}
server {
listen {{ nginx_template.server.port | default('80') }};
{% if 'tcp' not in nginx_template.server.options.proxy %}
server_name {{ nginx_template.server.server_name | default('default', true) }};
{% endif %}
{% endif %}
{% if 'gzip' in nginx_template.server.options %}
gzip on;
gzip_types text/plain text/css text/xml application/javascript application/x-javascript application/xml application/xml+rss application/emacscript application/json image/svg+xml;
gzip_proxied no-cache no-store private expired auth;
gzip_min_length 1000;
{% endif %}
{% if 'headers' in nginx_template.server.options %}
add_header X-Content-Type-Options "nosniff";
add_header X-Forwarded-Proto "https";
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=631138519";
{% endif %}
{% if 'access' in nginx_template.server.logging %}
access_log {{ nginx_template.server.logging.access }} main;
{% endif %}
{% if 'error' in nginx_template.server.logging %}
error_log {{ nginx_template.server.logging.error }};
{% endif %}
{% if 'syslog' in nginx_template.server.logging %}
access_log syslog:server={{ nginx_template.server.logging.syslog }},facility=local7,tag=nginx,severity=info;
{% endif %}
{% if 'tcp' not in nginx_template.server.options.proxy %}
location / {
{% endif %}
{% if 'timeouts' in nginx_template.server.options.proxy %}
{% if 'connect' in nginx_template.server.options.proxy.timeouts %}
proxy_connect_timeout {{ nginx_template.server.options.proxy.timeouts.connect | default("5s") }};
{% endif %}
{% if 'send' in nginx_template.server.options.proxy.timeouts %}
proxy_send_timeout {{ nginx_template.server.options.proxy.timeouts.send | default("300s") }};
{% endif %}
{% if 'read' in nginx_template.server.options.proxy.timeouts %}
proxy_read_timeout {{ nginx_template.server.options.proxy.timeouts.read | default("300s") }};
{% endif %}
{% endif %}
proxy_pass {% if 'tcp' not in nginx_template.server.options.proxy %}https://{% endif %}{{ nginx_template.upstream.name }};
{% if 'tcp' not in nginx_template.server.options.proxy %}
proxy_http_version 1.1;
{% if 'sni' in nginx_template.server.options.proxy %}
proxy_ssl_server_name on;
proxy_ssl_name {{ nginx_template.server.options.proxy.proxy_host | default("$host", true) }};
{% endif %}
{% if 'headers' in nginx_template.server.options.proxy %}
{% if 'connection' in nginx_template.server.options.proxy.headers %}
proxy_set_header Host {{ nginx_template.server.options.proxy.proxy_host | default("$host", true) }};
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade ";
{% endif %}
{% if 'client' in nginx_template.server.options.proxy.headers %}
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
{% endif %}
{% endif %}
}
{% endif %}
}
This gives me configs for that site. This works out great so we can make a bunch of yml files with our configs, and loop through them to generate our configs when we make a small change for the sites.