Hackアプリケーションをコンパイルして商用環境(コンテナ)で動かそう！

# Hack/HHVMの商用運用について

HHVMを商用環境や、または導入にあたり
本番向けのパフォーマンスチューニング等をしておく必要があります。

開発環境とは設定が異なりますので、今回はTypecheckerのオフと、
コンテナを使ってコンパイルしてシングルバイナリで動作させる方法を紹介していきます。

## 商用ではTypecheckerをオフにしよう

商用環境で動かす場合は、レビューなどを通っていることが前提です。
曖昧な型が入り込む可能性が非常に低いですが、
それは開発時に動作させているTypecheckerのおかげです。
そんなTypecheckerは商用で動作させる必要はありません。

下記の設定をphp.iniに追記してhhvmを起動させましょう。

```
hhvm.hack.lang.look_for_typechecker = false
```

もちろん開発時にオフにしてはいけません！

## JITコンパイラ

HHVMのJITコンパイラの設定は細かくすることができます。

[JIT Settings](https://docs.hhvm.com/hhvm/configuration/INI-settings#jit-settings)

## コンパイル

これはRepo Authoritativeと呼ばれるもので、
ソースコードをコンパイルして動かすモードです。
JITだけで早くするよりは、併用して効果があるものとなっています。

またLL言語でよくある商用環境で直接ファイルを操作するなどの事故もなくなりますので
安全にアプリケーションを運用することができます。


### 基本的な使い方

特定のディレクトリ配下を全てコンパイルするには下記のコマンドを実行します

```bash
$ hhvm --hphp -t hhbc --input-dir /path/to/root
```

マニュアルにも載っていますが、各フラグは下記の通りです

|    |    |
|----------|--------------|
| --hphp | HHVMでHackのコードを実行せずにオフラインで操作するためのフラグ | 
| -t | ターゲットを指定するもので、この場合はhhbcとなっており、これはHHVMバイトコードを意味します  |
|  --input-dir  | コンパイルソールコードがあるディレクトリを指定 |
| --output-dir |  コンパイルされたファイルの出力先を指定 これがないとtmp配下にランダムな文字列のディレクトリに吐き出します。  |

シンプルなアプリケーションであればこれだけで済みますが、
商用で動かすアプリケーションはそこまでシンプルなものはありません。
もうすこし細かい設定が必要です。

そんなときは下記のものを利用します

```bash
$ hhvm --hphp -t hhbc --module src --module vendor --ffile public/index.php
```

先ほどの例と少し変わりました。
先程の `--input-dir` は一つしか指定ができず、
実際には不要なファイルなども含めてしまうため、必要なものだけを含めるのがベストです。
そのため必要なファイルだけ指定するには上記のように細かく指定します。

|    |    |
|----------|--------------|
| --module | Hackのコードが含まれるディレクトトリを指定します。これは複数指定できます。 |
| --ffile | Hackのソースコードのファイルを指定  |

上記の例では`--ffile`でエントリポイントのスクリプトを指定、
アプリケーションの実装コードが含まれるディレクトリ、
composerでインストールしたライブラリが含まれるディレクトリを指定してコンパイルしています。

これで十分そうですね。
静的ファイル？
HHVMでは静的ファイルもコンパイルすることができます。

```bash
$ hhvm --hphp -t hhbc --module src --module vendor --ffile public/index.php \
  --file-cache /var/www/file.cache \
  --cfile public/static/image.png
  --cmodule public/images
```

利用できるものは以下の通りです。

|    |    |
|----------|--------------|
| --cfile | コンパイルに含めたい静的ファイルを指定 |
| --cmodule | コンパイルに含めたい静的ファイルが含まれるディレクトリを指定  |
| --file-cache | ファイルキャッシュを作成します  |

これさえ覚えておけばコンパイルできそうです。

## コンテナとバイトコード

コンパイルの方法がわかったところで、実際にコンテナと組み合わせてみます。
一般的なエントリポイントがpublic/index.hackで、srcとvendorディレクトリを使用しているアプリケーションを、
HHVM4.87でコンパイルします。
マルチステージビルドを使った例です。

```Dockerfile
FROM hhvm/hhvm-proxygen:4.86-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

COPY . /var/www
WORKDIR /var/www
RUN composer install --no-dev -o

FROM hhvm/hhvm-proxygen:4.87-latest AS build
COPY --from=dev / /
WORKDIR /var/www

RUN hhvm --hphp --target hhbc \
  -l3 \
  -d hhvm.check_return_type_hints=3 \
  --module src \
  --module vendor \
  --ffile public/index.hack \
  --output-dir /var/www \
  --file-cache /var/www/file.cache

RUN rm -rf vender \
  rm -rf src \
  rm -rf *yml \
  rm -rf /usr/local/bin/composer

FROM hhvm/hhvm-proxygen:4.87-latest AS prod
COPY --from=dev / /
```

dev向けには通常通りcomposerでinstallするところまでを含め、
buildではアプリケーションで必要なHackのコードなどをコンパイルします。
コンパイル後はcomposerやソースコードは不要なので削除します。
prodではコンパイル後に生成されるhhvm.hhbcをコピーしています。
実際にコンテナをECRなどに登録する場合はbuildでコンパイルされた状態で十分です。

試しにコンパイルして動かすには前回までに紹介したXHPのサンプルコードなども混ぜてみるといいでしょう。

これで動かす準備が整いました。
次にhhvm.hhbcをHHVMの本番向けiniに記述します。

```ini:php-prod.ini
date.timezone = Asia/Tokyo

hhvm.log.header = false
hhvm.debug.server_error_message = false
display_errors = Off
html_errors = Off
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.allow_run_as_root = true
hhvm.jit=1

hhvm.server.utf8ize_replace=true
hhvm.log.file=/dev/stderr
hhvm.admin_server.port=19001
hhvm.admin_server.password=SomePassword

hhvm.server.default_document=index.hack
hhvm.server.error_document404=index.hack
hhvm.server.thread_count=20
hhvm.jit_worker_threads=1
hhvm.jit_worker_arenas=1
hhvm.check_return_type_hints=3

hhvm.hack.lang.look_for_typechecker = false
hhvm.repo.authoritative=true
hhvm.repo.central.path=/var/www/hhvm.hhbc
hhvm.server.enable_static_content_from_disk=false
hhvm.server.file_cache=/var/www/file.cache
hhvm.server.source_root=/var/www
hhvm.virtual_host[default][path_translation]=public
```

コンパイルしたバイトコードを実行するには、`hhvm.repo.authoritative`を有効にします。
次にバイトコードのパスを `hhvm.repo.central.path` で指定します。
ファイルキャッシュがある場合は `hhvm.server.file_cache` で指定しておきましょう。
`hhvm.server.source_root`はソースコードのルートで、
`hhvm.virtual_host`はアクセスされた時にエントリポイントがあるディレクトリを指定します。
実際にファイルは存在していませんがバイトコード上に情報がありますので、
それと合わせるようにします。
もちろん`hhvm.server.default_document`も指定します。

手元で確認したい場合、docker-compose.ymlは下記のようにしておくといいでしょう。

```yaml
version: '3.8'
services:
  hhvm:
    build:
      context: .
      dockerfile: ./docker/hhvm/Dockerfile
      target: dev
    volumes:
      - .:/var/www
      - ./docker/hhvm/hh.conf:/etc/hh.conf
      - ./docker/hhvm/php.ini:/etc/hhvm/php.ini
      - ./docker/hhvm/server.ini:/etc/hhvm/server.ini
    command: hhvm --mode server -vServer.AllowRunAsRoot=1
    tty: true
    container_name: hhvm
    ports:
      - 19001:19001
    restart: always
  hhvm-build:
    build:
      context: .
      dockerfile: ./docker/hhvm/Dockerfile
      target: build
    volumes:
      - .:/var/www
      - ./docker/hhvm/hh.conf:/etc/hh.conf
      - ./docker/hhvm/php-prod.ini:/etc/hhvm/php.ini
      - ./docker/hhvm/server.ini:/etc/hhvm/server.ini
    tty: true
    container_name: hhvm
    ports:
      - 19001:19001
    restart: always
  hhvm-prod:
    build:
      context: .
      dockerfile: ./docker/hhvm/Dockerfile
      target: prod
    privileged: true
    volumes:
      - ./docker/hhvm/php-prod.ini:/etc/hhvm/php.ini
      - ./docker/hhvm/server.ini:/etc/hhvm/server.ini
    command: hhvm --mode server -d hhvm.log.level=verbose -vServer.AllowRunAsRoot=1 -vEval.HackCompilerWorkers=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

```

```bash
$ docker-compose up web-server hhvm-prod
```

などで手元で起動させてみてください。
バイトコードになったHackのコードがHHVM上で動いているのが確認できると思います。
当然コンパイルされたものですので、ファイルを変更しようが戻そうが反映されないことがわかります。
コンテナ技術が発展した環境では十分すぎるメリットがあります。
LL言語でありながらも静的型付け言語よりなHack/HHVMの利点でもありますので、
是非使ってみてください！