AWS Lambda Node.js dockerイメージでmecab (+neologd)を実行する

試験的にmecabが必要になったのですが、PHP8に対応していないので、EC2ではなく、Lambdaにインストールして、EC2のPHPから叩くようにしました。neologdができるようにしたのはおまけ。pythonがわからないので、nodeでイメージを作成しています。neologdの辞書まで含めた場合、数GB程度になりますが、LambdaのECRイメージデプロイは、10GBまで対応しているので、問題なくデプロイ、実行できました。Dockerなので、ローカル確認もできます。


ファイル構成

Dockerfile 
app.js
docker-compose.yml
package.json
deploy.sh

Dockerfile
基本イメージは、public.ecr.aws/lambda/nodejs:18 2023年9月17日現在、20は出ていませんでした。
makeに必要なライブラリをインストールして、makeする形。
ipadic-neologdは、gitプロトコルでcloneすると鍵の問題解決が難しそうだったので、https:からのclone。
cdコマンドも、Dockerfileは対応していないので、繋げる形で実行しました。

sedで /usr/local/etc/mecabrc を書き換えで、デフォルト辞書を ipadic →mecab-ipadic-neologd に変更しています。
```
FROM public.ecr.aws/lambda/nodejs:18
COPY *.js* package*.json .env ./

RUN yum install -y gcc gcc-c++ git patch tar make which find xz file openssl
RUN rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm

RUN mkdir ./mecab-service
RUN curl -L "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE" -o mecab-0.996.tar.gz
RUN tar zxvf mecab-0.996.tar.gz
RUN cd ./mecab-0.996 && ./configure --enable-utf8-only
RUN cd ./mecab-0.996 && make
RUN cd ./mecab-0.996 && make install

RUN curl -L "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM" -o mecab-ipadic-2.7.0-20070801.tar.gz
RUN tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz
RUN cd ./mecab-ipadic-2.7.0-20070801 && ./configure --with-charset=utf8
RUN cd ./mecab-ipadic-2.7.0-20070801 && make
RUN cd ./mecab-ipadic-2.7.0-20070801 && make install

RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
RUN cd mecab-ipadic-neologd && ./bin/install-mecab-ipadic-neologd -n -a -y
RUN sed -i -e s/ipadic/mecab-ipadic-neologd/g /usr/local/etc/mecabrc 


RUN npm install
ENV MY_ENV_VAR=value

CMD [ "app.lambdaHandler" ]
```

app.js

ライブラリとしてmecab-clientを使用。他に良い物が見当たりませんでした。デファクトスタンダードぽいのがあればいいのですが。

引数を、mecabで、解析して返すだけの実行です。デフォルトの辞書をつかうので、上記のDockerファイルでは、sedで書き換えました。

```

import { MeCab } from 'mecab-client'
export const lambdaHandler = async (event, context) => {
  console.log('%j',event)
  const mecab = new MeCab()
  const result = await mecab.parse(event.p)

  return result
};

```

docker-compose.yml
```
version: '3'
services:
  mecab:
    image: mecab
    build: .
    ports:
      - 9099:8080
    tty: true
    volumes:
      - "./data/:/var/task/data"
    working_dir: /var/task/
    env_file:
      - .env

```

package.json
mecab-clientのみ。
```
{
       "type": "module",
    "dependencies": {
        "mecab-client": "0.0.1"
    }
}
```


ビルド 任意のディレクトリで。そこそこ時間がかかります。
```
docker-compose up --build
Building mecab
[+] Building 1027.5s (24/24) FINISHED
 => [internal] load build definition from Dockerfile                                  0.0s
 => => transferring dockerfile: 32B                                                   0.0s
 => [internal] load .dockerignore                                                     0.1s
 => => transferring context: 2B                                                       0.0s
 => [internal] load metadata for public.ecr.aws/lambda/nodejs:18                      0.0s
 => CACHED [ 1/19] FROM public.ecr.aws/lambda/nodejs:18                               0.0s
 => [internal] load build context                                                     0.0s
 => => transferring context: 355B                                                     0.0s
 => [ 2/19] COPY *.js* package*.json .env ./                                          0.1s
 => [ 3/19] RUN yum install -y gcc gcc-c++ git patch tar make which find xz file op  45.4s
 => [ 4/19] RUN rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.  1.1s
 => [ 5/19] RUN mkdir ./mecab-service                                                 0.5s
 => [ 6/19] RUN curl -L "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh  2.7s
 => [ 7/19] RUN tar zxvf mecab-0.996.tar.gz                                           0.6s
 => [ 8/19] RUN cd ./mecab-0.996 && ./configure --enable-utf8-only                    5.1s
 => [ 9/19] RUN cd ./mecab-0.996 && make                                             33.4s
 => [10/19] RUN cd ./mecab-0.996 && make install                                      0.7s
 => [11/19] RUN curl -L "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh  5.0s
 => [12/19] RUN tar zxvf mecab-ipadic-2.7.0-20070801.tar.gz                           1.0s
 => [13/19] RUN cd ./mecab-ipadic-2.7.0-20070801 && ./configure --with-charset=utf8   1.7s
 => [14/19] RUN cd ./mecab-ipadic-2.7.0-20070801 && make                              1.6s
 => [15/19] RUN cd ./mecab-ipadic-2.7.0-20070801 && make install                      0.8s
 => [16/19] RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd  11.6s
 => [17/19] RUN cd mecab-ipadic-neologd && ./bin/install-mecab-ipadic-neologd -n -  903.0s
 => [18/19] RUN sed -i -e s/ipadic/mecab-ipadic-neologd/g /usr/local/etc/mecabrc      0.5s
 => [19/19] RUN npm install                                                           2.1s
 => exporting to image                                                               10.5s
 => => exporting layers                                                              10.4s
 => => writing image sha256:a0e9c6f22f1e49c3a363776b5f2d97c8f430265d25bf8b42ed660cdf  0.0s
 => => naming to docker.io/library/mecab                                              0.0s
Recreating mecab_mecab_1 ... done
Attaching to mecab_mecab_1
mecab_1  | 19 Sep 2023 02:30:26,880 [INFO] (rapid) exec '/var/runtime/bootstrap' (cwd=/var/task, handler=)
```

ローカル実行
https://engineering.linecorp.com/ja/blog/mecab-ipadic-neologd-new-words-and-expressions
新語・固有表現に強い「mecab-ipadic-NEologd」の効果を調べてみた （LINE Engineering）

恋ダンスが、固有名詞と判断されている。
```

curl -XPOST "http://localhost:9099/2015-03-31/functions/function/invocations" -d '{"p":"彼女はペンパイナッポーアッポーペンと恋ダンスを踊った。"}'
[
    {
        "surface": "彼女",
        "lexical": "名詞",
        "compound1": "代名詞",
        "compound2": "一般",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "彼女",
        "reading": "カノジョ",
        "pronunciation": "カノジョ"
    },
    {
        "surface": "は",
        "lexical": "助詞",
        "compound1": "係助詞",
        "compound2": "*",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "は",
        "reading": "ハ",
        "pronunciation": " ワ"
    },
    {
        "surface": "ペンパイナッポーアッポーペン",
        "lexical": "名詞",
        "compound1": "固有名詞",
        "compound2": "一般",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "Pen-Pineapple-Apple-Pen",
        "reading": "ペンパイナッポーアッポーペン",
        "pronunciation": "ペンパイナッポーアッ ポーペン"
    },
    {
        "surface": "と",
        "lexical": "助詞",
        "compound1": "並立助詞",
        "compound2": "*",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "と",
        "reading": "ト",
        "pronunciation": "ト"
    },
    {
        "surface": "恋ダンス",
        "lexical": "名詞",
        "compound1": "固有名詞",
        "compound2": "一般",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "恋ダンス",
        "reading": "コイダンス",
        "pronunciation": "コイダンス"
    },
    {
        "surface": "を",
        "lexical": "助詞",
        "compound1": "格助詞",
        "compound2": "一般",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "を",
        "reading": " ヲ",
        "pronunciation": "ヲ"
    },
    {
        "surface": "踊っ",
        "lexical": "動詞",
        "compound1": "自立",
        "compound2": "*",
        "compound3": "*",
        "conjugation": "五段・ラ行",
        "inflection": "連用タ接続",
        "original": "踊る",
        "reading": "オドッ",
        "pronunciation": "オドッ"
    },
    {
        "surface": "た",
        "lexical": "助動詞",
        "compound1": "*",
        "compound2": "*",
        "compound3": "*",
        "conjugation": "特殊・タ",
        "inflection": "基本形",
        "original": "た",
        "reading": "タ",
        "pronunciation": "タ"
    },
    {
        "surface": "。",
        "lexical": "記号",
        "compound1": "句点",
        "compound2": "*",
        "compound3": "*",
        "conjugation": "*",
        "inflection": "*",
        "original": "。",
        "reading": "。",
        "pronunciation": "。"
    }
]
```


ECRデプロイ
deploy.sh
```
docker build -t app-mecab .
docker tag app-mecab:latest 88*******.dkr.ecr.ap-northeast-1.amazonaws.com/app-mecab:latest
aws ecr get-login-password --region ap-northeast-1 --profile （任意のアクセスキー） | docker login --username AWS --password-stdin 88********.dkr.ecr.ap-northeast-1.amazonaws.com
aws ecr create-repository --repository-name app-mecab --profile （任意のアクセスキー）
docker push 88*******.dkr.ecr.ap-northeast-1.amazonaws.com/app-mecab:latest
```

Lambdaデプロイ
イメージデプロイにして、上記のイメージをECRから選択する。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83911/7becac5d-5481-8a83-5937-7b7be0a39f5a.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83911/2aa38392-53aa-fba2-5b65-c5d30e15acf7.png)

Lambda テスト
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83911/c47fcfaf-f237-7222-6fa0-dc05b866b848.png)

結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83911/e3d9b0e2-fac3-9709-4e25-b741ac0e63f5.png)
恋ダンスが、固有名詞と判断されている。

PHPから起動
歴史的経緯で、アクセスキーからの起動としていますが任意のcrendentialで大丈夫だと思います。
```

use Aws\Lambda\LambdaClient;



  protected function executeMecab($text)
    {
        $json = json_encode(["p"=>$text]);

        $lambdaFunctionName = {lambdaのファンクション名}
        if ($lambdaFunctionName === '') {
            return false;
        }
        $client = new LambdaClient([
            'region'  => 'ap-northeast-1',
            'version' => '2015-03-31',
            'credentials' => [
                'key' => env('AWS_ACCESS_KEY_ID'),
                'secret' => env('AWS_SECRET_ACCESS_KEY')
            ]
        ]);
        $invoke = $client->invoke(array(
            'FunctionName' => $lambdaFunctionName,
            'InvocationType' => 'RequestResponse', //同期実行
            'LogType' => 'Tail',
            'Payload' => $json
        ));
        $result = json_decode($invoke->get('Payload')->__toString());
        return $result;
    }

```

EC2にライブラリをインストールしたくない、手軽にmecabを試してみたいなどの用途に良いと思われます。