OpenrestyのDockerイメージにnjsモジュールを導入する

`nginx`で`Lua`を使いたいとなると`Openresty`が定番ですが最近はnginxで使える組み込み言語として`njs`というものがあります。

[nginx/njs: An official read\-only mirror of http://hg\.nginx\.org/njs/ which is updated hourly\.](https://github.com/nginx/njs)

`njs`は2016年に登場したNginx, Incが開発を進めているJavascriptのサブセットとなる言語です。
njsはlua同様に設定ファイルを動的に変更することが可能です。機能の豊富さなどではluaに軍配が上がりますが導入のしやすさあまり複雑なことをしない場合にはnjsも使いやすいのではないかと思います。
また、Web系のエンジニアとしてはluaよりもJavascriptの文法に馴染みがあると思うのでluaの学習コストを払いたくない場合などにnjsが選択肢になってくると思います。(自分はバックエンド系を中心に触ってきたのでJavascriptは趣味程度でしか分からないのでluaもJavascriptでもどっちも同レベルぐらいにしか書けませんが。)

そこで、luaとnjsの比較したいと思い調べてみたのですがlua,とnjsを同時に試すことが出来る環境が見つからなかったので自分でDockerで作ってみました。

- [openresty/openresty \- Docker Hub](https://hub.docker.com/r/openresty/openresty)

    - [openresty/docker\-openresty: Docker tooling for OpenResty](https://github.com/openresty/docker-openresty)

- [Compiling NGINX module as dynamic module for use in docker](https://gist.github.com/hermanbanken/96f0ff298c162a522ddbba44cad31081)

上のgistの記事を参考にopenrestyのイメージをベースに環境を作りました。

nginxで動的モジュールをビルド/導入する際に注意する点がいくつかあり、Nginx本体と同じバージョンでビルドしかつ`./configure`も同じにする必要があります。そして、`load_module`は設定ファイルの早い段階で読み込むする必要があります。

```Dockerfile
FROM openresty/openresty:1.15.8.2-alpine-fat AS builder

# Our NCHAN version
ENV NGINX_VERSION 1.15.8
ENV NJS_VERSION 0.2.8

# For latest build deps, see https://github.com/nginxinc/docker-nginx/blob/master/mainline/alpine/Dockerfile
RUN apk add --no-cache --virtual .build-deps \
  gcc \
  libc-dev \
  make \
  openssl-dev \
  pcre-dev \
  zlib-dev \
  linux-headers \
  curl \
  gnupg \
  libxslt-dev \
  gd-dev \
  geoip-dev

# Download sources
RUN wget "http://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz" -O nginx.tar.gz && \
    wget "https://github.com/nginx/njs/archive/${NJS_VERSION}.tar.gz" -O njs.tar.gz

# Reuse same cli arguments as the nginx:alpine image used to build
RUN CONFARGS=$(nginx -V 2>&1 | sed -n -e 's/^.*arguments: //p') \
	tar -zxC /usr/local -f nginx.tar.gz && \
    tar -xzvf "njs.tar.gz" -C /usr/local/nginx-${NGINX_VERSION} && \
    NJSDIR="/usr/local/nginx-${NGINX_VERSION}/njs-${NJS_VERSION}/nginx" && \
    cd /usr/local/nginx-${NGINX_VERSION} && \
   ./configure --with-compat $CONFARGS --add-dynamic-module=$NJSDIR && \
    make modules && make install

FROM openresty/openresty:1.15.8.2-6-alpine
# Extract the dynamic module NCHAN from the builder image
COPY --from=builder /usr/local/nginx/modules/ngx_http_js_module.so /usr/local/openresty/nginx/modules/ngx_http_js_module.so

COPY nginx.conf   /usr/local/openresty/nginx/conf/nginx.conf
COPY default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
STOPSIGNAL SIGTERM
CMD ["nginx", "-g", "daemon off;"]
```

```nginx:nginx.conf
# nginx.conf  --  docker-openresty
#
# This file is installed to:
#   `/usr/local/openresty/nginx/conf/nginx.conf`
# and is the file loaded by nginx at startup,
# unless the user specifies otherwise.
#
# It tracks the upstream OpenResty's `nginx.conf`, but removes the `server`
# section and adds this directive:
#     `include /etc/nginx/conf.d/*.conf;`
#
# The `docker-openresty` file `nginx.vh.default.conf` is copied to
# `/etc/nginx/conf.d/default.conf`.  It contains the `server section
# of the upstream `nginx.conf`.
#
# See https://github.com/openresty/docker-openresty/blob/master/README.md#nginx-config-files
#

#user  nobody;
worker_processes  1;

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

#pid        logs/nginx.pid;

load_module modules/ngx_http_js_module.so;

events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

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

    #access_log  logs/access.log  main;

    # See Move default writable paths to a dedicated directory (#119)
    # https://github.com/openresty/docker-openresty/issues/119
    client_body_temp_path /var/run/openresty/nginx-client-body;
    proxy_temp_path       /var/run/openresty/nginx-proxy;
    fastcgi_temp_path     /var/run/openresty/nginx-fastcgi;
    uwsgi_temp_path       /var/run/openresty/nginx-uwsgi;
    scgi_temp_path        /var/run/openresty/nginx-scgi;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}
```

```nginx:default.conf
# load_module modules/ngx_http_js_module.so;
js_include hello_world.js;

server {
	# see: http://nginx.org/en/docs/http/server_names.html
	server_name _;

	location = /favicon.ico { access_log off; log_not_found off; }

	location /hello {
		default_type text/html;
		content_by_lua '
			ngx.say("<p>Hello World</p>")
		';
	}

	location /js_hello {
		js_content hello;
	}
}
```

njsは設定ファイルにそのままJavascriptのコードを書くということが出来ないのでコードを別ファイルに切り出す必要があります。

```javascript:hello_world.js
function hello(r) {
    r.return(200, "Hello njs!");
}
```

この状態ビルドして`docker run`するときに自作アプリ用のnginx.confとJavascriptファイルをマウントして利用します。

```bash
# docker build
$ docker build -t njs .

# docker run
$ docker run -d --rm --name=my-njs -p 8082:80 \
      -v $(pwd)/default.conf:/etc/nginx/conf.d/default.conf  \
      -v $(pwd)/hello_world.js:/usr/local/openresty/nginx/conf/hello_world.js \
      -it njs
```

動作確認として立ち上げたDockerコンテナにアクセスしてみます。

```bash
$ curl 192.168.99.100:8082/hello
<p>Hello World</p>

$ curl 192.168.99.100:8082/js_hello
Hello njs!
```
エラーも出ずちゃんとレスポンスが返ってきました。

以上、Openrestyにnjsの動的モジュールを導入するためのTipsでした。
今回利用したファイル一覧はこちら。

- [gist](https://gist.github.com/yuokada/15ba7e400035d72edf76b162e969e9aa)