👛

NginxでLua使おう、ついでにredisも

2022/08/13に公開

Luaを試すに至るまで

nginxでcache周りで少し複雑な事をしたいけどnginxの素の機能では実現出来ない、luaを使えば出来そうだ、でも新しくluaを覚えるとか面倒だな、もしかしてcaddyならいけるかも?ダメだった。
という感じで楽したくて調べていたものの結局答えはluaだった。

Luaって難しいの?

nginxで使う程度の事ならbashスクリプトよりは簡単そう。luaのドキュメント一切見なくても公式サンプル[1]のコードを参考にすれば書ける程度。

コメントアウトは少し特殊で、
一行コメントは

--こうやって一行コメントにする

複数行コメントは

--[[ 
こうやって
複数行の
コメントアウト
]]

環境構築

dockerを使う。使うイメージはopenrestyredismemcachedだ。openrestyがnginxの立ち位置になる。
なおopenrestyを使わない場合、ubuntuのイメージに対してluaを有効にしつつnginxをインストールする必要がある。
※詳しくは以下のリンク参照

コピペで使えるdocker-compose。今回はnginxとredisのみ使用。

docker-compose.yaml
version: "3.9"
services:
  redis:
    image: "redis:alpine"
    expose:
      - 6379
    healthcheck:
      test: ["CMD", "redis-cli", "--raw", "incr", "ping", "|", "grep", "PONG"]
      timeout: 5s
      retries: 5
      start_period: 5s
  memcached:
    image: "memcached:alpine"
  mysql:
    image: "mysql:8.0"
    environment:
      MYSQL_ROOT_PASSWORD: example
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
  nginx:
    image: "openresty/openresty:alpine"
    ports: 
      - 80:80
    volumes:
      - ./nginx/nginx.conf:/usr/local/openresty/nginx/conf/nginx.conf
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      redis:
        condition: service_healthy
      memcached:
        condition: service_started
      mysql:
        condition: service_started

Luaの前にngx_redis2モジュールの動作確認

redisへのsetとget用のlocationを追加して

./nginx/default.conf(抜粋)
    #redis
    location ~* /redis/(.*)/(.*) {
        redis2_query set $1 $2;
        redis2_pass redis_cluster;
    }
    location ~* /redis/(.*) {
        redis2_query get $1;
        redis2_pass redis_cluster;
    }

dockerを起動して

$ docker-compose up -d

set用URLにアクセス

$ curl localhost/redis/color/white
+OK

get用URLにアクセス

$ curl localhost/redis/color
$5
white

redis-cliでの確認もしてみる

$ redis-cli keys "*"
1) "OS"
2) "color"

$ redis-cli get color
"white"

nginxで設定したURLを叩くだけでredisにキー&バリューのset&get出来る事が確認出来た。

Luaの動作確認

日本語の公式風なドキュメント[1:1]を見ながらconfを書き換える

その1. ログ出力

./nginx/default.conf(抜粋)
    location /lua-log {
        content_by_lua_block {
            ngx.log(ngx.INFO, "ログチェック");
        }
    }

アクセスすると

[info] 7#7: *2 [lua] content_by_lua(default.conf:153):2: ログチェック, client: 172.25.0.1, server: localhost, request: "GET /lua-log HTTP/1.1", host: "localhost"

こんなログが出る

その2. say

./nginx/default.conf(抜粋)
    location /lua-say {
        content_by_lua_block {
            ngx.say( "セイセイする");
        }
    }

アクセスすると

セイセイする

sayした文字列がまんま返ってくる

その3.header

./nginx/default.conf(抜粋)
    location /lua-header {
        content_by_lua_block {
            ngx.header["Content-Type"] = "text/html; charset=UTF-8";
            ngx.say( "ヘッダーをセットだ");
        }
    }

ブラウザーからアクセスすると

ヘッダーをセットだ

sayした文字列がまんま返ってくる

その4.luaとredis2

conf(抜粋)
    location /redis-internal {
        internal;
        set_unescape_uri $query $arg_query;
        set_unescape_uri $req $arg_req;
        if ($request_method ~ ^(POST|PUT|PATCH)$ ) {
            set_unescape_uri $cmd $arg_cmd;
            set_unescape_uri $key $arg_key;
            set_unescape_uri $value $arg_value;
            redis2_query $cmd samplekey $query;
            redis2_query $cmd requestbody $req;
            redis2_query $cmd $key $value;
            redis2_pass redis_cluster;
        }
        if ($request_method ~ ^(GET)$ ) {
            redis2_query get OS;
            redis2_pass redis_cluster;
        }
    }

    location /lua-redis {
        lua_need_request_body on;
        client_max_body_size 50k;
        client_body_buffer_size 50k;

        content_by_lua_block {
            if (ngx.var.request_method == ngx.HTTP_GET) then
                local time = ngx.time()
                local res = ngx.location.capture("/redis-internal",
                    {
                        method = "GET",
                        args = {
                            query = 'lua-redis',
                            req = ngx.var.request_method,
                            word = "FREEWORD"
                        }
                    }
                )
                ngx.log(ngx.INFO, res.body)
                ngx.say(res.body)
            else
                local time = ngx.time()
                local res = ngx.location.capture("/redis-internal",
                    {
                        method = ngx.HTTP_POST,
                        args = {
                            cmd = 'set',
                            query = 'lua-redis setbylua',
                            req = ngx.var.request_method,
                            key = ngx.req.get_headers()["Key"],
                            value = ngx.req.get_headers()["Value"],
                            word = "FREEWORD"
                        }
                    }
                )
                ngx.log(ngx.INFO, res.body)
                ngx.say(res.body)
            end
        }
    }

RedisへSet

curl -X POST -H "Key: name" -H "Value: l-freeze" -i localhost/lua-redis

RedisからGet

$ curl -X GET -H "Key: name" -i localhost/lua-redis

redis-cliでも確認する

redis-cli get "name"
"l-freeze"

その5.luaでredis

conf(luaのみ抜粋)
    location /lua-de-redis {
        content_by_lua_block {
            --local cjson = require "cjson"
            local redis = require "resty.redis"
            local red = redis:new()
            local ok, err = red:connect("redis", 6379)
            if not ok then
                ngx.say("Failed to connect: ", err)
                return
            else
                ngx.log(ngx.INFO,"Connection: " .. ok)
            end

            local ans, err = red:set("resty-redis-key", "resty-redis-value")
            if not ans then
                ngx.say("Failed to set: ", err)
                return
            else
                ngx.log(ngx.INFO,"Set: " .. ans)
            end

            local res, err= red:get("resty-redis-key")
            if not res then
                ngx.say("Failed to get: ", err)
                return
            else
                ngx.log(ngx.INFO,"Get: " .. res)
            end

            ngx.log(ngx.INFO, "[REDIS]" .. res)
            ngx.say("[REDIS]" .. res)
            --red:close()
            
        }
    }

:::

curlで確認

curl -X GET localhost/lua-de-redis
HTTP/1.1 200 OK
Server: openresty/1.21.4.1
Date: Sat, 13 Aug 2022 07:48:58 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive

[REDIS]resty-redis-value

redis-cliでも確認

/data # redis-cli keys "*"
1) "OS"
2) "color"
3) "name"
4) ""
5) "set"
6) "resty-redis-key"
7) "samplekey"
8) "requestbody"

/data # redis-cli get "resty-redis-key"
"resty-redis-value"

nginxのログを確認

log
2022/08/13 07:46:42 [info] 7#7: *4 [lua] content_by_lua(default.conf:277):10: Connection: 1, client: 172.25.0.1, server: localhost, request: "GET /lua-de-redis HTTP/1.1", host: "localhost"
2022/08/13 07:46:42 [info] 7#7: *4 [lua] content_by_lua(default.conf:277):18: Set: OK, client: 172.25.0.1, server: localhost, request: "GET /lua-de-redis HTTP/1.1", host: "localhost"
2022/08/13 07:46:42 [info] 7#7: *4 [lua] content_by_lua(default.conf:277):26: Get: resty-redis-value, client: 172.25.0.1, server: localhost, request: "GET /lua-de-redis HTTP/1.1", host: "localhost"
2022/08/13 07:46:42 [info] 7#7: *4 [lua] content_by_lua(default.conf:277):29: [REDIS]resty-redis-value, client: 172.25.0.1, server: localhost, request: "GET /lua-de-redis HTTP/1.1", host: "localhost"

参考にした資料

脚注
  1. 公式の日本語版 ↩︎ ↩︎

Discussion