GolangでMeCab+NEologdが使えるDockerイメージ作成してみた

#はじめに
社内のサーバーサイド公式言語である[Golang](https://golang.org/)で、
形態素解析ライブラリの`MeCab+NEologd`を試したいな〜
という情熱が燃えたぎったので、挑戦してみました。

Dockerfile、初体験であります。(ドキドキ)

#TL;DR
- `main.go`の用意
- `docker-compose.yml`の用意
- `Dockerfile`の作成
- `startup.sh`の作成

#`main.go`
[go gin](https://github.com/gin-gonic/gin)のフレームワークを使って、REST APIを作成し、テキストを受け取るとともにMeCabによる形態素解析の結果を返す`main.go`です。
（※エディタは`goland`を使いました。`Go Modules`とかの管理が楽）

```golang:main.go
package main

import (
	"fmt"
	"log"
	"net/http"

	mecab "github.com/bluele/mecab-golang"
	"github.com/gin-gonic/gin"
)

const (
	ipadic = "/usr/local/lib/mecab/dic/mecab-ipadic-neologd"
)

func main() {
	// Start HTTP server
	r := gin.Default()
	r.POST("/analyze", morphologicalAnalyze)
	if err := r.Run(":8080"); err != nil {
		log.Fatal(err)
	}
}

func morphologicalAnalyze(c *gin.Context) {
	text := c.PostForm("text")
	// mecab here
	mecab, err := mecab.New("-Owakati")
	if err != nil {
		panic(err)
	}
	defer mecab.Destroy()
	data := parseToNode(mecab, text)
	if err != nil {
		c.JSON(http.StatusInternalServerError, "Something went wrong")
		return
	}
	c.IndentedJSON(http.StatusOK, data)
}

func parseToNode(m *mecab.MeCab, text string) []map[string]interface{} {
	tg, err := m.NewTagger()
	if err != nil {
		panic(err)
	}
	defer tg.Destroy()
	lt, err := m.NewLattice(text)
	if err != nil {
		panic(err)
	}
	defer lt.Destroy()
	node := tg.ParseToNode(lt)

	data := make([]map[string]interface{}, 0)
	for {
		entry := make(map[string]interface{})
		fmt.Printf("%s\t%s\n", node.Surface(), node.Feature())
		entry[node.Surface()] = node.Feature()
		data = append(data, entry)
		if node.Next() != nil {
			break
		}
	}
	return data
}
```
※動かなかったらごめんなさいorz

#`docker-compose.yml`
goのAPIサーバーのコンテナを立ち上げる`docker-compose`ファイルです。
超軽量の`Alpine Linux`の`Go 1.15.10`のイメージを使ってコンテナを立ち上げます。

```yaml:docker-compose.yml
version: "3.8"

services:
api:
    image: go-mecab-challenge:golang-1.15.10-alpine3.12
    build:
      context: ./docker/go
      dockerfile: Dockerfile
    container_name: api
    ports:
      - 8080:8080
    volumes:
      - ./api:/go/src/github.com/go-mecab-challenge/api
      - /Users/username/go:/go
    working_dir: /go/src/github.com/go-mecab-challenge/api/src
    environment:
      - APP_STAGE=local
      - LOG_LEVEL=debug
```
必要なフォルダをdocker側にマウントする。


#`Dockerfile`
まずDockerfileをビルドして、コンテナを立ち上げる事前準備をします。
（※ちょっと時間かかる）

```bash
$ docker build ./docker/go # Dockerfileのあるディレクトリ指定
```

`Alpine Linux`は超軽量なので、無駄なコマンドやファイル等が削られている状況。
なので、必要なコマンドであったりを`apk add`でインストールしてカスタマイズが必要。
※下記、不要なものもあるはずなので、ご査収下さい。

```dockerfile:Dockerfile
FROM golang:1.15.10-alpine3.12

ENV MECAB_VERSION 0.996
ENV IPADIC_VERSION 2.7.0-20070801
ENV MECAB_URL https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE
ENV IPADIC_URL https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM

RUN apk update && \
    apk upgrade && \
    apk add --no-cache build-base curl git bash openssl openssh-client sudo && \
    curl -SL -o mecab-${MECAB_VERSION}.tar.gz ${MECAB_URL} && \
    tar zxf mecab-${MECAB_VERSION}.tar.gz && \
    cd mecab-${MECAB_VERSION} && \
    ./configure --enable-utf8-only --with-charset=utf8 && \
    make && \
    make install && \
    cd && \
    curl -SL -o mecab-ipadic-${IPADIC_VERSION}.tar.gz ${IPADIC_URL} && \
    tar zxf mecab-ipadic-${IPADIC_VERSION}.tar.gz && \
    cd mecab-ipadic-${IPADIC_VERSION} && \
    ./configure --with-charset=utf8 && \
    make && \
    make install && \
    cd && \
    git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git /mecab-ipadic-neologd && \
    /mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n -a -y -p /usr/local/lib/mecab/dic/mecab-ipadic-neologd && \
    echo "dicdir = /usr/local/lib/mecab/dic/mecab-ipadic-neologd" > /usr/local/etc/mecabrc && \
    rm -rf \
      mecab-${MECAB_VERSION}* \
      mecab-${IPADIC_VERSION}* \
      /mecab-ipadic-neologd

COPY startup.sh /startup.sh
RUN chmod 744 /startup.sh
CMD ["/startup.sh"]
```
###解説
- 環境変数として、`MeCab`や辞書`ipadic`の情報を設定
- `alpine`のパッケージ管理コマンド`apk`で必要なコマンド、ファイル等をインストール
    - コマンドたち：`curl git bash openssl sudo`
    - `openssh-client`: ssh接続に使える `ssh hostname`とか
    - `build-base`: `binutils, file, gcc, g++, make, libc-dev, fortify-headers, patch` を含む
        - `binutils `: GNUが提供しているツールのうち、バイナリのプログラミングを実装するためのツール
        - `gcc`: GNU Compiler Collection （グニューコンパイラコレクション） のこと
        - `g++`: [GNU C++コンパイラ] (https://ja.wikipedia.org/wiki/GNU%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - `libc-dev`: Linux カーネル由来のユーザスペースヘッダ群を提供するパッケージ。インストール済みの `GNU libc` 用ヘッダやほかの システムライブラリ用ヘッダで利用するそう
- `MeCab`が保存されている`google drive`に直接アクセスして`tar.gz`をとってくる
-　`tar.gz`を解凍して展開
- `MeCab`のコンパイル用の設定(`configure`)をして、コンパイル(`make`)→インストール(`make install`)
- `ipadic`のインストール。`MeCab`と同じ手順
- `NeoLogd`のインストール：gitリポジトリをとってきて、その中のコマンドを叩いてダウンロードする
- 一旦不要になったフォルダやファイルを`rm`する
- `startup.sh`でAPIサーバー起動

###ローカルへのMeCabインストールはこちらを参考ください
https://qiita.com/ichi_zamurai/items/6311614f83fc30dd28d2

#立ち上げCMDはShellScriptに別で記述
```shell:startup.sh
#!/bin/bash
export CGO_LDFLAGS="-L/usr/local/Cellar/mecab/0.996/lib -lmecab -lstdc++"
export CGO_CFLAGS="-I/usr/local/Cellar/mecab/0.996/include"
go run main.go --host 0.0.0.0 --port 8080
```
###解説と懸念点
- `MeCab`自体はC言語で書かれたライブラリらしく、go言語で使用する時には`CGO`という機能を使う必要があり、二つのパスを`export`させてからGo実行している。
- 実際にローカルでメカブを`$ go get github.com/bluele/mecab-golang`するときも、上記の`export`が必要。
    - 参考：[bluele/mecab-golang](https://github.com/bluele/mecab-golang#getting-started)
- ただ、実行しているdockerコンテナ上に上記のパスがなかったんだけど、やってる意味あるのかなあ。。。という懸念点です。（要調査）

```shell
$ docker exec -it api /bin/bash # apiはコンテナ名。実行しているコンテナ内に潜り込む
```

#形態素解析「すもももももももものうち」
いざ桃太郎、出陣！！
goのAPIを叩いて、すももちゃんを投げてみる。

![スクリーンショット 2021-03-19 18.23.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/0ddd05ea-8b9c-c9f9-1d53-b6915793ceda.png)

おお、できた。
ああ、でも、`NEologd`をつかったら、なんだか一つの固有名詞になっちゃった。

実際にローカルで普通に やってみると(ipadic)

```shell
$ echo "すもももももももものうち" | mecab
すもも	名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
も	助詞,係助詞,*,*,*,*,も,モ,モ
もも	名詞,一般,*,*,*,*,もも,モモ,モモ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
うち	名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS
```
ってなったのになあ。

辞書の使い分けが必要っぽいなこれ。

もうひとつw

![スクリーンショット 2021-03-19 18.59.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1075243/95f2cace-2425-acee-91dd-6f35976a8349.png)

###「サンタさん立ったのたったの三本指で」

#まとめ
`[GO+MeCab+Neologd+Docker]`という念願のアプリケーションを作ることができました。
まあ実用化には一切届かないけど、やっぱり立ち上げって大変だ。けど、面白い。

あとは想像力と努力で、アプリケーションを育てていくしかない。(∵)

例えば、各単語をポイント付けして、
固有名詞だけ重くして、バズりワードの解析とか、やったら面白そう。


以上ありがとうございました。

##こちらを参考にDockerfile作成させていただきました♪
わかりやすかったです。
参考：[Alpine LinuxでMeCab with NEologd](https://qiita.com/nownabe/items/4171776aec1f05de9f28)
参考：[DockerでMeCab / Word2Vecの環境を構築する](https://blog.70-10.net/2020/12/27/docker-mecab-word2vec/)