Skip to content

Commit

Permalink
feat: use an optional shared Redis cache for multiple NGINX instances
Browse files Browse the repository at this point in the history
* docker compose example development configuration
* Redis cache documentation
  • Loading branch information
dhhyi authored and shauke committed Nov 29, 2023
1 parent 7d7c8ec commit e266e28
Show file tree
Hide file tree
Showing 12 changed files with 203 additions and 13 deletions.
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
*.properties
*.crt
*.key
*.lua
.*ignore
.husky
.gitattributes
Expand Down
4 changes: 4 additions & 0 deletions .vscode/intershop.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ appserver
backoffice
bar
bars
bitnami
blocklist
breakline
categoryref
Expand Down Expand Up @@ -41,6 +42,7 @@ errorpage
extralarge
fastpay
fieldgroup
flushdb
fontawesome
foo
foobar
Expand Down Expand Up @@ -142,6 +144,7 @@ quoterequest
quoterequests
rebased
recaptcha
rediss
refactoring
refactorings
resetted
Expand All @@ -167,6 +170,7 @@ sitekey
sku
skus
spgid
srcache
sslmode
storefinder
stylelint
Expand Down
7 changes: 7 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ services:
#
# </CDN-Example>

# redis:
# image: bitnami/redis
# container_name: redis
# environment:
# - ALLOW_EMPTY_PASSWORD=yes

nginx:
image: intershophub/intershop-pwa-nginx
build: nginx
Expand All @@ -71,6 +77,7 @@ services:
UPSTREAM_PWA: 'http://pwa:4200'
NGINX_ENTRYPOINT_QUIET_LOGS: ANYVALUE
CACHE: 0
# REDIS_URI: redis://redis:6379
# LOG_ALL: off
# SSL: 1
# SSR: 0
Expand Down
61 changes: 61 additions & 0 deletions docs/guides/nginx-startup.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,67 @@ If the cache feature is switched off, all caching for pre-rendered pages is disa
The cache duration for pre-rendered pages can be customized using `CACHE_DURATION_NGINX_OK` (for successful responses) and `CACHE_DURATION_NGINX_NF` (for 404 responses).
The value supplied must be in the `time` format that is supported by [nginx proxy_cache_valid](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_valid).

### Shared Redis Cache

Multiple nginx instances can share the same Redis cache if this feature is activated.
To use the shared Redis cache, the environment variable `REDIS_URI` must be provided with a valid Redis Entrypoint.

The Redis URI has the following format: `redis://USERNAME:PASSWORD@HOST:PORT/DB`

To use a secure connection, use `rediss://` instead of `redis://`.
The parameters `USERNAME`, `PASSWORD` and `DB` are optional.

The current implementation supports only Redis deployments with a single entrypoint.
Redis Cluster setup is not supported because the redirect handling is not implemented.
Connecting to Redis Sentinel is also not supported.
For production setups, we recommend using a Redis cloud service.

#### Cache timing

The cache duration for pre-rendered pages can be customized using `CACHE_DURATION_NGINX_OK`.
The value is transferred to [srcache_default_expire](https://github.com/openresty/srcache-nginx-module#srcache_default_expire).
Using `CACHE_DURATION_NGINX_NF` is not supported with Redis cache.
404 pages are cached with the same duration as successful responses.

#### Clearing the Redis Cache

Because the cache is no longer embedded into the nginx container, cache clearing has to be done separately.
Use the `redis-cli` to send a `flushdb` command to the Redis instance.
This can be done using docker with the following command:

```bash
docker run --rm -it bitnami/redis redis-cli -u <REDIS_URI> flushdb
```

#### Redis for Development

For development environments a local Redis can be easily started with the example `docker-compose.yml` configuration.

```yaml
redis:
image: bitnami/redis
container_name: redis
environment:
- ALLOW_EMPTY_PASSWORD=yes
---
nginx:
environment:
CACHE: 1
REDIS_URI: redis://redis:6379
```

Passing extra command-line flags to the Redis service for configuration (see [Redis configuration](https://redis.io/docs/management/config/)) can be done via docker `command`.

```yaml
redis:
...
command: /opt/bitnami/scripts/redis/run.sh --loglevel debug
```

An additional Redis service is not intended for production environments where a Redis cloud service should be used.
For that reason also the PWA Helm chart does not support deploying an own Redis service with the PWA.
Only the `redis.uri` can be configured for a Helm deployment.

## Further References

- [Concept - Multi-Site Handling](../concepts/multi-site-handling.md)
Expand Down
8 changes: 6 additions & 2 deletions nginx/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
FROM openresty/openresty:1.21.4.3-1-jammy
COPY --from=nginx:mainline /docker-entrypoint.sh /
COPY --from=nginx:mainline /docker-entrypoint.d/*.sh /docker-entrypoint.d/
RUN cp /usr/local/openresty/nginx/conf/mime.types /etc/nginx/mime.types \
&& useradd -Ms /bin/false nginx
COPY lua/*.lua /usr/local/openresty/lualib/
RUN ln -s /usr/local/openresty/nginx/conf/mime.types /etc/nginx/ \
&& useradd -Ms /bin/false nginx \
&& mkdir -p /etc/resolvconf/resolv.conf.d \
&& echo "nameserver 8.8.8.8" >> /etc/resolvconf/resolv.conf.d/tail \
&& luarocks install lua-resty-redis-connector
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]

Expand Down
3 changes: 3 additions & 0 deletions nginx/features/cache-off.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
map "" $cache_status {
default "BYPASS";
}
7 changes: 7 additions & 0 deletions nginx/features/cache.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
proxy_cache_path /tmp/cache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
proxy_cache my_cache;
proxy_cache_use_stale error timeout http_404 http_500 http_502 http_503 http_504;

map "$upstream_cache_status:$srcache_fetch_status" $cache_status {
":BYPASS" "BYPASS";
"~HIT" "HIT";
"~^MISS" "MISS";
default "-";
}
35 changes: 35 additions & 0 deletions nginx/lua/redis-integration.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
local function extract_ssl(uri)
if string.sub(uri,1,9) == "rediss://" then
return true, string.gsub(uri, "rediss://", "redis://")
else
return false, uri
end
end

local function connect()
local uri = os.getenv("REDIS_URI")
if not uri then
ngx.log(ngx.ERR, "REDIS_URI not set")
ngx.exit(500)
end

local ssl, uri = extract_ssl(uri)

local redis, err = require("resty.redis.connector").new({
url = uri,
connection_options = {
ssl = ssl
}
}):connect()

if err ~= nil then
ngx.log(ngx.ERR, "Failed to connect to redis, error -> ", err)
ngx.exit(500)
end

return redis
end

return {
connect = connect
}
5 changes: 3 additions & 2 deletions nginx/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ events {
worker_connections 1024;
}

env REDIS_URI;

http {
include /etc/nginx/mime.types;
Expand Down Expand Up @@ -54,14 +55,14 @@ http {
'"status": "$upstream_status", '
'"response_time": "$upstream_response_time", '
'"response_length": "$upstream_response_length", '
'"cache_status": "$upstream_cache_status"'
'"cache_status": "$cache_status"'
'}'
'}';

log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$upstream_response_time "$upstream_cache_status"';
'$upstream_response_time "$cache_status"';

sendfile on;

Expand Down
49 changes: 49 additions & 0 deletions nginx/templates/cache-locations.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{- if and (getenv "CACHE" | strings.ToLower | regexp.Match "on|1|true|yes") (getenv "REDIS_URI") -}}
location /redis-fetch {
internal;

resolver local=on ipv6=off;
resolver_timeout 5s;

content_by_lua_block {
local key = assert(ngx.var.arg_key, "no key found")

local red = require("redis-integration").connect()
local data, err = red:get(key)
assert(red:set_keepalive(10000, 100))
if err ~= nil then
ngx.log(ngx.ERR, "Failed to get data from redis, error -> ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
elseif data == ngx.null then
ngx.exit(ngx.HTTP_NOT_FOUND)
else
ngx.print(data)
ngx.exit(ngx.HTTP_OK)
end
}
}

location /redis-store {
internal;

resolver local=on ipv6=off;
resolver_timeout 5s;

content_by_lua_block {
local value = assert(ngx.req.get_body_data(), "no value found")
local key = assert(ngx.var.arg_key, "no key found")
local exptime = assert(ngx.var.arg_exptime, "no exptime found")

local red = require("redis-integration").connect()
local ok, err = red:set(key, value)
if not ok then
ngx.log(ngx.ERR, "Failed to set data to redis, error -> ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
else
assert(red:expire(key, exptime))
assert(red:set_keepalive(10000, 100))
ngx.exit(ngx.HTTP_OK)
end
}
}
{{- end -}}
24 changes: 24 additions & 0 deletions nginx/templates/cache-settings.conf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{{- if getenv "CACHE" | strings.ToLower | regexp.Match "on|1|true|yes" -}}
{{- if getenv "REDIS_URI" -}}

srcache_response_cache_control off;
srcache_store_statuses 200 302 404;

srcache_default_expire {{ getenv "CACHE_DURATION_NGINX_OK" }};

# make a nice cache key without parameters
set_sha1 $key $scheme://$host$uri$c_uri;

srcache_fetch GET /redis-fetch key=$key;
srcache_store PUT /redis-store key=$key&exptime=$srcache_expire;
{{- else -}}
proxy_ignore_headers Cache-Control;
proxy_cache_valid 200 302 {{ getenv "CACHE_DURATION_NGINX_OK" }};
proxy_cache_valid 404 {{ getenv "CACHE_DURATION_NGINX_NF" }};

# make a nice cache key without parameters
proxy_cache_key $scheme://$host$uri$c_uri;
{{- end -}}

add_header X-Cache-Status $cache_status;
{{- end -}}
12 changes: 3 additions & 9 deletions nginx/templates/multi-channel.conf.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,14 @@
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $thescheme;

add_header X-Cache-Status $upstream_cache_status;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 200 302 {{ getenv "CACHE_DURATION_NGINX_OK" }};
proxy_cache_valid 404 {{ getenv "CACHE_DURATION_NGINX_NF" }};

add_header X-icm-channel {{ $channel }} always;
add_header X-icm-default-lang {{ $lang }} always;
add_header X-ua-device $ua_device always;

rewrite ^.*$ $ssr_rewrite;
set $c_uri $ssr_curi;

# make a nice cache key without parameters
proxy_cache_key $scheme://$host$uri$c_uri;
include /etc/nginx/conf.d/cache-settings.conf;

rewrite ^.*/index.html$ {{ $baseHref }}/loading;

Expand Down Expand Up @@ -99,7 +93,7 @@ server {
access_log /dev/stdout {{ getenv "LOGFORMAT" }} if=$loggable;

include /etc/nginx/conf.d/cache-blacklist.conf;

include /etc/nginx/conf.d/cache-locations.conf;

{{- if getenv "OVERRIDE_IDENTITY_PROVIDERS" }}
{{- range $domainOverride, $identyProviderOverride := (ds "overrideIdentityProviders") }}
Expand Down Expand Up @@ -157,7 +151,7 @@ server {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $thescheme;

add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Status $cache_status;

proxy_pass {{ $UPSTREAM_PWA }};
proxy_buffer_size 128k;
Expand Down

0 comments on commit e266e28

Please sign in to comment.