diff --git a/ansible/group_vars/varnish.yml b/ansible/group_vars/varnish.yml new file mode 100644 index 0000000..ebe3b4b --- /dev/null +++ b/ansible/group_vars/varnish.yml @@ -0,0 +1,7 @@ +varnish_major: 60lts +varnish_cfg_path: /etc/varnish +haproxy_traefik_port: 80 +haproxy_traefik_ip: + - 10.24.3.6 + - 10.24.3.3 + - 10.25.3.4 diff --git a/ansible/plays/varnish.yml b/ansible/plays/varnish.yml index 6d1ef2c..43db9c8 100644 --- a/ansible/plays/varnish.yml +++ b/ansible/plays/varnish.yml @@ -2,8 +2,8 @@ - name: Install and configure systemd for varnish hosts: varnish roles: - - role: roles/firewall - enable_80_ufw_port: true - enable_443_ufw_port: true - - - role: roles/varnish \ No newline at end of file +# - role: roles/firewall +# enable_80_ufw_port: true +# enable_443_ufw_port: true +# + - role: roles/varnish diff --git a/ansible/roles/varnish/handlers/main.yml b/ansible/roles/varnish/handlers/main.yml new file mode 100644 index 0000000..7cefc5d --- /dev/null +++ b/ansible/roles/varnish/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: reload varnish + service: + name: varnish + state: reloaded + diff --git a/ansible/roles/varnish/tasks/copy-source.yml b/ansible/roles/varnish/tasks/copy-source.yml new file mode 100644 index 0000000..f30ced8 --- /dev/null +++ b/ansible/roles/varnish/tasks/copy-source.yml @@ -0,0 +1,46 @@ +--- +- file: + path: "/etc/varnish" + state: directory + owner: root + group: root + mode: "0755" + +- template: + src: default.vcl.j2 + dest: "{{ varnish_cfg_path }}/default.vcl" + owner: root + group: root + mode: "0644" +# validate: "haproxy -c -f %s" + notify: reload varnish + +- template: + src: vcl_deliver.vcl.j2 + dest: "{{ varnish_cfg_path }}/vcl_deliver.vcl" + owner: root + group: root + mode: "0644" +# validate: "haproxy -c -f %s" + notify: reload varnish + +- file: + path: "/etc/varnish/includes" + state: directory + owner: root + group: root + mode: "0755" + +- template: + src: includes/x-cache-header.vcl.j2 + dest: "{{ varnish_cfg_path }}/includes/x-cache-header.vcl" + owner: root + group: root + mode: "0644" +# validate: "haproxy -c -f %s" + notify: reload varnish + +- service: + name: varnish + state: restarted + diff --git a/ansible/roles/varnish/tasks/install.yml b/ansible/roles/varnish/tasks/install.yml new file mode 100644 index 0000000..44027f0 --- /dev/null +++ b/ansible/roles/varnish/tasks/install.yml @@ -0,0 +1,113 @@ +--- +- name: Ensure apt cache is up to date (pre) + ansible.builtin.apt: + update_cache: true + cache_valid_time: 3600 + +- name: Debian only - ensure debian-archive-keyring is installed + ansible.builtin.apt: + name: debian-archive-keyring + state: present + when: ansible_facts.distribution == "Debian" + +- name: Ensure required tools are installed (curl, gnupg, apt-transport-https) + ansible.builtin.apt: + name: + - curl + - gnupg + - apt-transport-https + state: present + +# Packagecloud repo parameters: +# os = "debian" or "ubuntu" +# dist = codename (e.g. bookworm, bullseye, focal, jammy, noble) +# :contentReference[oaicite:1]{index=1} +- name: Set packagecloud repo parameters + ansible.builtin.set_fact: + varnish_pkgcloud_os: "{{ 'ubuntu' if ansible_facts.distribution == 'Ubuntu' else 'debian' }}" + varnish_pkgcloud_dist: "bookworm" + # varnish_pkgcloud_dist: "{{ ansible_facts.distribution_release }}" + +# ---- apt >= 1.1 path (keyrings + signed-by) ---- +- name: Ensure /etc/apt/keyrings exists + ansible.builtin.file: + path: /etc/apt/keyrings + state: directory + mode: "0755" + +- name: Download packagecloud GPG key (ascii) + ansible.builtin.get_url: + url: https://packagecloud.io/varnishcache/varnish{{ varnish_major }}/gpgkey + dest: /tmp/varnishcache_varnish{{ varnish_major }}.gpgkey + mode: "0644" + +- name: Dearmor packagecloud key into /etc/apt/keyrings + ansible.builtin.command: > + gpg --dearmor -o /etc/apt/keyrings/varnishcache_varnish{{ varnish_major }}-archive-keyring.gpg + /tmp/varnishcache_varnish{{ varnish_major }}.gpgkey + args: + creates: /etc/apt/keyrings/varnishcache_varnish{{ varnish_major }}-archive-keyring.gpg + +- name: Ensure Sequoia crypto-policy directory exists + ansible.builtin.file: + path: /etc/crypto-policies/back-ends + state: directory + owner: root + group: root + mode: "0755" + +- name: Allow SHA1 signatures for sequoia (packagecloud compatibility) + ansible.builtin.copy: + dest: /etc/crypto-policies/back-ends/sequoia.config + owner: root + group: root + mode: "0644" + backup: true + content: | + [hash_algorithms] + sha1 = "always" + +- name: Add Varnish 6.0 LTS repo + ansible.builtin.apt_repository: + repo: "deb [signed-by=/etc/apt/keyrings/varnishcache_varnish{{ varnish_major }}-archive-keyring.gpg] https://packagecloud.io/varnishcache/varnish60lts/{{ varnish_pkgcloud_os }}/ {{ varnish_pkgcloud_dist }} main" + filename: varnishcache_varnish{{ varnish_major }} + state: present + +- name: Add Varnish 6.0 LTS source repo (optional) + ansible.builtin.apt_repository: + repo: "deb-src [signed-by=/etc/apt/keyrings/varnishcache_varnish{{ varnish_major }}-archive-keyring.gpg] https://packagecloud.io/varnishcache/varnish60lts/{{ varnish_pkgcloud_os }}/ {{ varnish_pkgcloud_dist }} main" + filename: varnishcache_varnish{{ varnish_major }} + state: present + when: + - varnish_enable_deb_src | default(false) + +- name: Update apt cache (after adding repo) + ansible.builtin.apt: + update_cache: true + +- name: Install Varnish Cache 6.0 LTS + ansible.builtin.apt: + name: "{{ varnish_packages | default(['varnish']) }}" + state: present + + +- name: Copy systemd template + become: true + ansible.builtin.template: + src: varnish-systemd.j2 + dest: /lib/systemd/system/varnish.service + owner: root + mode: "0644" + +- name: Restart systemd daemon + become: true + ansible.builtin.systemd: + daemon_reload: yes + +- name: Reload varnish service + become: true + ansible.builtin.systemd: + name: varnish.service + state: reloaded + + diff --git a/ansible/roles/varnish/tasks/main.yml b/ansible/roles/varnish/tasks/main.yml index a94ea58..feaad07 100644 --- a/ansible/roles/varnish/tasks/main.yml +++ b/ansible/roles/varnish/tasks/main.yml @@ -1,57 +1,2 @@ ---- - - name: update apt - become: true - apt: - update_cache: yes - cache_valid_time: 86400 - - - name: install required packages - package: - name: - - debian-archive-keyring - - curl - - gnupg - - apt-transport-https - - - name: add varnish apt key & repo - block: - - name: add varnish key - apt_key: - url: https://packagecloud.io/varnishcache/varnish60lts/gpgkey - state: present - - - name: add varnish repo - apt_repository: - repo: 'deb https://packagecloud.io/varnishcache/varnish60lts/{{ varnish_release }} {{ varnish_release_codename }} main' - state: present - - - name: add varnish repo src - apt_repository: - repo: 'deb-src https://packagecloud.io/varnishcache/varnish60lts/{{ varnish_release }} {{ varnish_release_codename }} main' - state: present - - - name: update apt - become: true - apt: - update_cache: yes - cache_valid_time: 86400 - - - name: install varnish package - package: - name: varnish - - - name: copy systemd template - template: - src: varnish-systemd.j2 - dest: /lib/systemd/system/varnish.service - owner: root - mode: 644 - - - name: restart systemd daemon - systemd: - daemon_reload: yes - - - name: restart varnish service - systemd: - name: varnish.service - state: reloaded +- import_tasks: install.yml +- import_tasks: copy-source.yml diff --git a/ansible/roles/varnish/templates/default.vcl.j2 b/ansible/roles/varnish/templates/default.vcl.j2 new file mode 100644 index 0000000..68ad712 --- /dev/null +++ b/ansible/roles/varnish/templates/default.vcl.j2 @@ -0,0 +1,206 @@ +vcl 4.1; + +import std; +import directors; + +include "vcl_deliver.vcl"; +include "includes/x-cache-header.vcl"; + +{% for ip in haproxy_traefik_ip %} +backend bk_appsrv_static-{{ loop.index }} { + .host = "{{ ip }}"; + .port = "{{ haproxy_traefik_port }}"; + .connect_timeout = 3s; + .first_byte_timeout = 10s; + .between_bytes_timeout = 5s; + .probe = { + .url = "/ping"; + .expected_response = 404; + .timeout = 1s; + .interval = 3s; + .window = 2; + .threshold = 2; + .initial = 2; + } +} + +{% endfor %} + +/* + * Who is allowed to PURGE + */ +acl purge { + "127.0.0.1"; + "localhost"; + # add your admin / app hosts here +} + +sub vcl_init { + new vdir = directors.round_robin(); +{% for ip in haproxy_traefik_ip %} + vdir.add_backend(bk_appsrv_static-{{ loop.index }}); +{% endfor %} +} + +sub vcl_recv { + ### Default options + + # Health Checking + if (req.url == "/varnishcheck") { + return (synth(200, "health check OK!")); + } + + # Set default backend + set req.backend_hint = vdir.backend(); + + # grace period (stale content delivery while revalidating) + set req.grace = 30s; + + # Purge request + if (req.method == "PURGE") { + if (client.ip !~ purge) { + return (synth(405, "Not allowed.")); + } + return (purge); + } + + # Accept-Encoding header clean-up + if (req.http.Accept-Encoding) { + # use gzip when possible, otherwise use deflate + if (req.http.Accept-Encoding ~ "gzip") { + set req.http.Accept-Encoding = "gzip"; + } elsif (req.http.Accept-Encoding ~ "deflate") { + set req.http.Accept-Encoding = "deflate"; + } else { + # unknown algorithm, remove accept-encoding header + unset req.http.Accept-Encoding; + } + + # Microsoft Internet Explorer 6 is well know to be buggy with compression and css / js + if (req.url ~ "\.(css|js)(\?.*)?$" && req.http.User-Agent ~ "MSIE 6") { + unset req.http.Accept-Encoding; + } + } + + # Enable debug headers through query param + if (req.url ~ "(?i)debug=(true|yes|1)") { + set req.http.X-debug = true; + } + + ### Per host/application configuration + # bk_appsrv_static + # Stale content delivery + if (std.healthy(req.backend_hint)) { + set req.grace = 30s; + } else { + set req.grace = 1d; + } + + # Cookie ignored in these static pages + unset req.http.Cookie; + + ### Common options + # Static objects are first looked up in the cache + if (req.url ~ "\.(png|gif|jpg|swf|css|js)(\?.*)?$") { + return (hash); + } + + # Default: look for the object in cache + return (hash); +} + +sub vcl_hash { + hash_data(req.url); + + if (req.http.host) { + hash_data(req.http.host); + } else { + hash_data(server.ip); + } +} + +/* + * Called after a successful PURGE + */ +sub vcl_purge { + return (synth(200, "Purged.")); +} + +sub vcl_backend_response { + # Stale content delivery + set beresp.grace = 1d; + + # Hide Server information + unset beresp.http.Server; + + # Store compressed objects in memory (gzip at fetch time) + # Varnish can deliver gunzipped/gzipped depending on client support + if (beresp.http.Content-Type ~ "(?i)(text|application)") { + set beresp.do_gzip = true; + } + + ################### + # cache rules # + ################### + # HTML pages → short cache or no cache + if (bereq.url ~ "\.html$") { + set beresp.ttl = 30s; # Cache briefly + set beresp.uncacheable = true; # Or disable cache entirely + } + + # JavaScript & CSS → long cache + if (bereq.url ~ "\.(js|css)$") { + set beresp.ttl = 1d; + } + + # Images under /image/ → long cache + if (bereq.url ~ "^/images/.*\.(svg|png|jpe?g)$") { + set beresp.ttl = 1y; + } + + # Favicons → long cache + if (bereq.url ~ "^/favicons/") { + set beresp.ttl = 1y; + } + + # Fallback: ensure some cache + if (beresp.ttl <= 0s) { + set beresp.ttl = 22s; + } + + set beresp.http.X-TTL = beresp.ttl; + + # remove any cookie on static or pseudo-static objects + unset beresp.http.Set-Cookie; + + return (deliver); +} + +sub vcl_deliver { + # unset resp.http.Via; + unset resp.http.X-Varnish; + + # Handle conditional request with ETag + if ( + req.http.If-None-Match && + req.http.If-None-Match == resp.http.ETag + ) { + return (synth(304)); + } + + return (deliver); +} + +sub vcl_synth { + if (resp.status == 304) { + set resp.http.ETag = req.http.If-None-Match; + # set resp.http.Content-Length = "0"; + return (deliver); + } + + # Keep defaults; this replaces the old vcl_error. + # (Your old "obj.status == 751" special case isn't referenced anywhere + # in the provided VCL, so it was dropped.) + return (deliver); +} + diff --git a/ansible/roles/varnish/templates/includes/x-cache-header.vcl.j2 b/ansible/roles/varnish/templates/includes/x-cache-header.vcl.j2 new file mode 100644 index 0000000..53045d4 --- /dev/null +++ b/ansible/roles/varnish/templates/includes/x-cache-header.vcl.j2 @@ -0,0 +1,43 @@ +sub vcl_recv { + unset req.http.X-Cache; +} + +sub vcl_hit { + set req.http.X-Cache = "HIT"; +} + +sub vcl_miss { + set req.http.X-Cache = "MISS"; +} + +sub vcl_pass { + set req.http.X-Cache = "PASS"; +} + +sub vcl_pipe { + set req.http.X-Cache = "PIPE uncacheable"; +} + +sub vcl_synth { + set resp.http.X-Cache = "SYNTH"; +} + +sub vcl_deliver { + if (obj.uncacheable) { + set req.http.X-Cache = req.http.X-Cache + " uncacheable" ; + } else { + set req.http.X-Cache = req.http.X-Cache + " cached" + " (real age: " + resp.http.Age + ", hits: " + obj.hits + ", ttl: " + regsub(resp.http.x-ttl, "\..*", "") + ")"; + } + + # if we are gracing, make sure the browser doesn't cache things, and set our maxage to 1 + # also log grace delivery + if (req.http.graceineffect) { + set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "max-age=[0-9]*", "max-age=1"); + set resp.http.Cache-Control = regsub(resp.http.Cache-Control, "channel-maxage=[0-9]*", "channel-maxage=1"); + set req.http.X-Cache = req.http.X-Cache + " [grace: " + req.http.graceineffect + " " + req.http.grace + ", remaining: " + req.http.graceduration + "]"; + } + + # uncomment the following line to show the information in the response + set resp.http.X-Cache = req.http.X-Cache; +} + diff --git a/ansible/roles/varnish/templates/vcl_deliver.vcl.j2 b/ansible/roles/varnish/templates/vcl_deliver.vcl.j2 new file mode 100644 index 0000000..de3db1d --- /dev/null +++ b/ansible/roles/varnish/templates/vcl_deliver.vcl.j2 @@ -0,0 +1,40 @@ +sub vcl_deliver { + # Happens when we have all the pieces we need, and are about to send the + # response to the client. + + if (resp.status == 503) { + set resp.http.failing-backend = "true"; + } + + # Give some debug + if (req.http.X-debug && req.esi_level == 0) { + set resp.http.X-Backend = req.backend_hint; + set resp.http.X-Backend-Url = req.url; + set resp.http.X-Varnish-Server = server.hostname; + } else { + # not debug, strip some headers + unset resp.http.X-Cache; + unset resp.http.X-Backend; + unset resp.http.x-upstream; + unset resp.http.x-request-uri; + unset resp.http.Via; + unset resp.http.xkey; + unset resp.http.x-goog-hash; + unset resp.http.x-goog-generation; + unset resp.http.X-GUploader-UploadID; + unset resp.http.x-goog-storage-class; + unset resp.http.x-goog-metageneration; + unset resp.http.x-goog-stored-content-length; + unset resp.http.x-goog-stored-content-encoding; + unset resp.http.x-goog-meta-goog-reserved-file-mtime; + unset resp.http.Server; + unset resp.http.X-Apache-Host; + unset resp.http.X-Varnish-Backend; + unset resp.http.X-Varnish-Host; + unset resp.http.X-Nginx-Host; + unset resp.http.X-Upstream-Age; + unset resp.http.X-Retries; + unset resp.http.X-Varnish; + } +} +