HHVM(proxygen) 環境を構築してみよう

# HHVM構築

ひと昔前はHHVMを用意するのにも大変な時代がありましたが、
現在はDockerを利用して簡単に構築することができます。
今回は一からHHVM開発環境を作成してみましょう！

HHVMのコンテナ自体はDocker Hubにて公式が公開しています。

[hhvm/hhvm-proxygen](https://hub.docker.com/r/hhvm/hhvm-proxygen/)

本番運用などを考えると、fastcgiで動かすメリットはあまりありません。
proxygenを利用することで、HHVM自体の管理機能やパフォーマンス面でもアドバンテージがあります。
(実際のコンテナ運用ライクな内容は別で紹介します。)

GCP/AWSなどで運用する場合はhhvm-proxygen単体でも十分ですが、
今回はNginxをフロントに置き、upstreamでhhvm-proxygenに流す構成にします。
(log収集などもそちらの方が楽でしょう！)

## HHVM container

まずはhhvm/hhvm-proxygen:latestを利用します。

```dockerfile:docker/hhvm/Dockerfile
FROM hhvm/hhvm-proxygen:latest AS dev

RUN apt-get update
RUN DEBIAN_FRONTEND=noninteractive
RUN apt install -y dnsutils iputils-ping net-tools

RUN hhvm --version && php --version
RUN cd $(mktemp -d) \ 
 && curl https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

```

hhvmはUbuntuで動作する様になっているため、alpineなどの提供はありません。
そのため、サイズはちょっとデカめです。

次にHHVMのphp.iniを用意します。

```ini:docker/hhvm/php.ini
date.timezone = Asia/Tokyo

hhvm.log.header = true
hhvm.debug.server_error_message = true
display_errors = On
html_errors = On
error_reporting = 22527

hhvm.server.fix_path_info = true
hhvm.server.type = proxygen
hhvm.server.port = 18080
hhvm.log.use_log_file = true
hhvm.server.source_root = /var/www/public

hhvm.php_file.extensions[hack]=1 
hhvm.jit=1

hhvm.server.default_document = "index.hack"
hhvm.server.error_document404 = "index.hack"
hhvm.server.utf8ize_replace=true
hhvm.log.file=/dev/stderr

hhvm.admin_server.port=19001
hhvm.admin_server.password=SomePassword
```

ここで重要なのは、`hhvm.server.source_root`です。
proxygenを使ってHackをアプリケーションサーバとして動かすため、
Hackのエントリポイントになるファイルが設置してあるディレクトリを指定します。
エントリポイントのファイルは`hhvm.server.default_document`で指定します。

`hhvm.server.port`はproxygenを通信させるポートです。

またHHVMの監視やプロセスの管理などは
HHVMで用意されているAdmin Serverを利用した方が便利です。
これを利用するために`hhvm.admin_server.port`、`hhvm.admin_server.password`を記述します。

当然コンテナを利用しますので、logは`hhvm.log.file=/dev/stderr`とします。

## Nginx container

nginxはalpineなどの軽量なものを利用しましょう。
先にconfを用意しておきましょう。

```conf:docker/nginx/conf.d/hhvm.conf
upstream hhvm-proxygen {
    server hhvm:18080;
    keepalive 300;
}

server {
    listen 80;
    server_name _;
    charset utf-8;

    location / {
        proxy_pass http://hhvm-proxygen;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        send_timeout 180;
        proxy_connect_timeout 300;
        proxy_read_timeout    300;
        proxy_send_timeout    300;
    }
}

```

hhvm-proxygenコンテナの名前をhhvmとして記述しています。
AWS Fargateなどで動かす場合は適宜変更してください。
nginx.confもコンテナ運用しやすい様にログフォーマットなどを変更しておきましょう。

```conf:docker/nginx/nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

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

    log_format json escape=json '{'
        '"time": "$time_local",'
        '"remote_addr": "$remote_addr",'
        '"host": "$host",'
        '"remote_user": "$remote_user",'
        '"status": "$status",'
        '"server_protocol": "$server_protocol",'
        '"request_method": "$request_method",'
        '"request_uri": "$request_uri",'
        '"request": "$request",'
        '"body_bytes_sent": "$body_bytes_sent",'
        '"request_time": "$request_time",'
        '"upstream_response_time": "$upstream_response_time",'
        '"http_referer": "$http_referer", '
        '"http_user_agent": "$http_user_agent",'
        '"http_x_forwarded_for": "$http_x_forwarded_for",'
        '"http_x_forwarded_proto": "$http_x_forwarded_proto"'
    '}';

    access_log /dev/stdout json;
    error_log  /dev/stderr warn;
    sendfile        on;
    keepalive_timeout  65;
    gzip  off;

    proxy_buffering off;
    proxy_cache off;
    proxy_http_version 1.1;
    proxy_set_header  Connection '';
    proxy_set_header  Host               $host;
    proxy_set_header  X-Real-IP          $remote_addr;
    proxy_set_header  X-Forwarded-Host   $host;
    proxy_set_header  X-Forwarded-Server $host;
    proxy_set_header  X-Forwarded-For    $proxy_add_x_forwarded_for;

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

```

これでログ収集などに対応しやすくなるでしょう！

nginx自体はここでは`nginx:1.19-alpine`を利用します。

```dockerfile:docker/nginx/Dockerfile
FROM nginx:1.19-alpine

RUN apk --update add tzdata && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    apk del tzdata && \
    rm -rf /var/cache/apk/*

RUN rm /etc/nginx/nginx.conf /etc/nginx/conf.d/default.conf
RUN echo "Not Found" > /usr/share/nginx/html/index.html
COPY docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY docker/nginx/conf.d/ /etc/nginx/conf.d/
```

最低限のものだけやっておきます。
これでコンテナの準備ができました。

## Docker Compose

次にdocker-compose.ymlを用意しておきます。

```yaml:docker-compose.yml
version: '3.8'
services:
  hhvm:
    build:
      context: .
      dockerfile: ./docker/hhvm/Dockerfile
    volumes:
      - .:/var/www
      - ./docker/hhvm/hh.conf:/etc/hh.conf
      - ./docker/hhvm/php.ini:/etc/hhvm/php.ini
    command: hhvm --mode server -vServer.AllowRunAsRoot=1
    tty: true
    container_name: hhvm
    ports:
      - 19001:19001
    restart: always
  web-server:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
    ports:
      - 80:80
    container_name: web-server
    tty: true
```

特筆すべき点は特にありませんが、ここで指定している`19001`は、管理用のAdmin Serverになりますので、
実際に運用する場合は内部通信のみ許可をするなどインターネットには公開しない様にしましょう。
hhvm-proxygenは外部に許可せず、Nginxのみと通信するようにしています。
NginxやApache HTTP Serverを置かない場合はhhvm-proxygenのポートも記述しておきましょう。

## Hackを実行する

ここまで用意ができたら`hhvm.server.source_root`で指定したディレクトリに、
`hhvm.server.default_document`で指定したファイルを作成しておきましょう。
ファイルは`index.hack`としましょう！

```hack:public/index.hack
use function phpversion;

<<__EntryPoint>>
async function main(): Awaitable<void> {
  echo phpversion();
}
```

エントリポイントとなるファイルにアクセスすると、
`<<__EntryPoint>>`Attributeを指定した関数が自動で動く仕組みになっています。
他の言語でおなじみのmain関数と同じ扱いにできる、ということになります。

ファイルを用意したら起動させましょう。

```bash
# build
$ docker-compose build --no-cache

# boot
$ docker-compose up -d  
```

起動できたら、`http://127.0.0.1/`などにアクセスしてみましょう。
ブラウザなどで下記の出力がされれば問題ありません！

![スクリーンショット 2020-12-03 23.24.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/32814/f7d26c54-e664-4109-40a1-99858b0e7a3a.png)

次にAdmin Serverにアクセスしてみましょう。

php.iniで指定した`hhvm.admin_server.port`、
`hhvm.admin_server.password`を利用します。
設定例では`http://127.0.0.1:19001`となりますので、19001にアクセスします。

画面上にたくさんの管理用エンドポイントが表示されていれば設定OKです。
例としてサーバステータスを取得したい場合は、
`hhvm.admin_server.password`で指定したパスワードを使ってアクセスします。
アクセスする場合は下記の通りです。
`http://127.0.0.1:19001/status.json?auth=SomePassword`

HHVMのステータス情報がJSONで表示されます。
これらの情報を用いて実運用などに備えるといいでしょう！

今回は商用で実際にHHVMをコンテナで運用できる様に簡単な設定を紹介しました。
次回をお楽しみに！