在Nginx中,对每个请求执行任意命令

首先

自宅の Raspberry Pi で個人用の Web サーバを立てる機会がありました。リクエストの直前に任意のコマンドが実行できたら便利だなと思い調べてみたところ、ngx_http_lua_module が使えそうだということがわかりました。

今回は例として、Nginx へのリクエストの直前に git pull を実行する方法について紹介します1。もちろん任意のコマンドが実行可能なので、git pull 以外のコマンドを実行することもできます。

安装OpenResty

使用する Web サーバは Nginx なのですが、Nginx ではなく OpenResty をインストールします。OpenResty は Nginx に ngx_lua や LuaJIT などのモジュールを組み込んだ改良版のようなものです。そのため使い勝手は通常の Nginx とほぼ同じです。

今回は ngx_http_lua_module というモジュールを使用したいので OpenResty をインストールします。公式の Nginx にこのモジュールを組み込むこともできますが、環境構築が大変なので非推奨とされています。

インストール手順については OpenResty 公式のダウンロードページ に、それぞれの OS やディストリビューション、アーキテクチャごとにわかりやすく掲載されているので、こちらを参照してください。参考までに、Raspberry Pi (aarch64) + Ubuntu 22 での環境構築の例 を以下の示します。

# Ubuntu 22 (aarch64)

# すでに公式の Nginx がインストールされている場合は先に無効化する (アンインストールはしなくても良い)
sudo systemctl disable nginx
sudo systemctl stop nginx

# --no-install-recommends はつけなくても良い
sudo apt -y install wget gnupg ca-certificates

wget -O - https://openresty.org/package/pubkey.gpg | sudo gpg --dearmor -o /usr/share/keyrings/openresty.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/openresty.gpg] http://openresty.org/package/arm64/ubuntu $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/openresty.list > /dev/null
sudo apt update
sudo apt -y install openresty

安装完成后,我认为Web服务器已经启动。我们来访问一下,确认一下。

curl -I http://localhost
HTTP/1.1 200 OK
Server: openresty/1.21.4.1
Date: Sun, 22 Jan 2023 04:07:15 GMT
Content-Type: text/html
Content-Length: 1097
Last-Modified: Tue, 17 May 2022 03:51:45 GMT
Connection: keep-alive
ETag: "62831bd1-449"
Accept-Ranges: bytes

如果HTTP状态为200 OK,并且服务器为OpenResty,则安装成功。

如果没有启动,请启动守护程序然后再试一次。

sudo systemctl start openresty

环境设置

从这里开始,我们将进行环境设置。

编辑现有文件

默认的配置文件位于 /usr/local/openresty/nginx/conf/ 内。请注意,它与官方的 Nginx 位置不同。

我认为在此目录下有 /usr/local/openresty/nginx/conf/nginx.conf 文件。我将对其进行以下更改。

user を nobody から一般ユーザ名に変更

conf.d 内をファイルをロードするように変更

具体的做法如下。

-#user nobody;
+user ubuntu;

http {
+    include /usr/local/openresty/nginx/conf/conf.d/*.conf;
}

ubuntu はユーザ名の例です

include は http ディレクティブの中に書いてください。

ちなみに /usr/local/openresty/nginx/conf/nginx.conf.default というファイルが初期状態では /usr/local/openresty/nginx/conf/nginx.conf と全く同じなので自分で事前にバックアップを取っておく必要はありません。

创建配置文件

接下来,创建一个/usr/local/openresty/nginx/conf/conf.d/目录,并在其中创建一个以.conf为扩展名的文件。文件名可以任意命名。

sudo mkdir /usr/local/openresty/nginx/conf/conf.d

新しく作成したファイルの中に Web サーバの設定を書きます。この設定は通常の Nginx とほぼ同じです。以下は、あらかじめ 4000 番ポートで立てておいた静的サイトジェネレータ (Jekyll や Hugo など) のサーバを Nginx でリバースプロキシするための設定の例です。

server {
    listen 80;
    server_name localhost;

    location / {
        try_files $uri @proxy;
    }

    location @proxy {
        access_by_lua_block {
            -- access_by_lua_block (Lua) 内でのコメントは "#" ではなく "--" を使うことに注意!
            os.execute('/usr/local/openresty/nginx/conf/bin/git-pull /path/to/your-git-directory')
        }

        proxy_set_header  Host $http_host;
        proxy_set_header  X-Real-IP $remote_addr;
        proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass        http://localhost:4000;
    }
}

关于 access_by_lua_block

现在,这里有与通常的Nginx配置不同的部分。这是access_by_lua_block指令。该指令不是Nginx语法,而是ngx_http_lua_module专用的指令。因此,在通常的Nginx中尝试加载这个配置文件将会导致错误,但如果在集成了ngx_http_lua_module的OpenResty中就可以加载。

并且,该模块的行为是当请求指定的页面,通过location块进入时,会执行access_by_lua_block指令中编写的Lua代码。Lua是一种编程语言。换句话说,通过使用该模块,可以在每次请求到达时执行任意的Lua代码。

只需要一个选项,“因此,本次要执行Shell脚本,所以使用os.execute。通过在参数中写入命令或脚本来执行它。”

创建执行脚本

在os.execute内直接写git pull,虽然可以实现,但存在几个问题。

    • コマンドを変更するたびに OpenResty (Nginx) のサーバを再起動しなければならない

 

    CSS や JavaScript、ファビコンなどのリクエストに対しても反応するので一回のアクセスで何回もコマンドが実行されてしまう

そこで、この設定ファイルの外にシェルスクリプト実行用のファイルを作っておき、複数回の実行を制御するような処理を入れておくことにおき上記の問題を回避します。

シェルスクリプトを置く場所はどこでも良いのですがここでは例として /usr/local/openresty/nginx/conf/bin/ とします。ファイル名は例として git-pull とします。

安装以下类型的Shell脚本。

#!/bin/sh

# Usage:
#   git-pull <GIT_DIRECTORY>

set -eu

THRESHOLD="60"
LAST_UPDATE=".git-fetch-last-update"

main() {
  parse_args "$@"

  if [ ! -f "$dir"/$LAST_UPDATE ]; then
    pull
  fi

  duration=$(echo "scale=10; $(awk 'BEGIN{ print srand(srand()) }') - $(cat "$dir"/$LAST_UPDATE)" | bc | sed 's/^\./0./' | sed 's/\.[0-9,]*$//g')

  if [ "$duration" -ge $THRESHOLD ]; then
    pull
  else
    echo "info: skip pulling because it has not been $THRESHOLD seconds yet since the last update"
  fi
}

pull() {
  awk 'BEGIN{ print srand(srand()) }' > "$dir"/$LAST_UPDATE
  git -C "$dir" pull > /dev/null 2>&1 &
}

parse_args() {
  if [ "$1" != "" ]; then
    dir="$1"
  else
    echo "error: missing git directory" >&2
    exit 1
  fi
}

main "$@"

举动如下所示。

    • バックグラウンドで git pull を実行する

フォアグラウンドで実行するとリモートサーバ (GitHub など) との通信で OpenResty (Nginx) へのアクセスが妨げられてしまうため

ただし直近 60 秒以内に git pull をすでに実行していた場合は何もしない

请根据需要适当更改细节的路径和常数。

语法检查

当设置完成后,请进行语法检查。请注意使用的不是nginx命令,而是openresty。

sudo openresty -t
nginx: the configuration file /usr/local/openresty/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/openresty/nginx/conf/nginx.conf test is successful

如果出现语法正确的提示,则重新启动服务器以反映更改的设置。如果出现错误提示,请检查是否有拼写错误、路径指定错误等问题。

重新启动

为了使设置更改生效,我们将重新启动服务器。请注意,这里的命令名称仍然是 openresty。

sudo systemctl restart openresty

确认动作

首先,在您的 Git 目录 (/path/to/your-git-directory) 中,确保远程仓库中存在附加提交(远程仓库处于 ahead 状态)。

その後、静的サイトジェネレータで立てたサーバのサイトにアクセスしてみてください。5 〜 10 秒ほど経ったあと、該当リポジトリで新しいコミットが追加されていれば成功です。なお、説明が重複しますが、シェルスクリプト内で直近 60 秒以内に git pull していた場合は何もしないという制御を入れているので、連続で何度もアクセスしても git pull は実行されません。

调试方法

当事情不顺利时,使用错误日志可能会很有帮助。 要输出错误日志,请按以下方式进行更改。

-#error_log  logs/error.log;
-#error_log  logs/error.log  notice;
-#error_log  logs/error.log  info;
+error_log  logs/error.log;
+error_log  logs/error.log  notice;
+error_log  logs/error.log  info;

如果在os.execute命令或脚本中有错误输出,将被记录到OpenResty(Nginx)的错误日志中。默认的日志文件路径是/usr/local/openresty/nginx/logs/error.log。

使用less命令的+F选项可以实时查看日志,非常方便。

less +F /usr/local/openresty/nginx/logs/error.log

最后一点

本次介绍了OpenResty的安装方法和ngx_http_lua_module的使用方法。由于使用体验与Nginx几乎相同,并且可以使用方便的模块,所以我认为在仅在本地使用的网站或进行测试运营时,使用OpenResty也是可以的。

请参考以下网站

    • openresty/lua-nginx-module

 

    • OpenResty® Linux Packages

 

    • Directives – OpenResty Reference

 

    • How to run a shell script on every request?

 

    • How to use content_by_lua and proxy_pass together ?

 

    • Programming in Lua : 1.3

 

    failed to load external Lua file
這個是假設在本地建立個人靜態網站後,當有人存取時自動更新並進行編譯的功能。如果在公開的互聯網伺服器上進行這個操作,每次有人存取時都會進行 git pull,可能會給像 GitHub 這樣的伺服器帶來負擔,請注意。這個功能僅僅適用於非公開伺服器。↩雖然在 access_by_lua_block 中有類似的 content_by_lua_block,但請注意後者不能與 proxy_pass 等內容處理指令一起使用。↩

在Lua中,註釋使用的是 –,而不是 #。請注意。↩

順便提一下,我剛才將 /usr/local/openresty/nginx/conf/nginx.conf 中的 user 更改為普通用戶,如果不進行這個更改,執行命令時可能以 nobody 身份執行,導致權限問題無法正常執行。↩

广告
将在 10 秒后关闭
bannerAds