サーバーレスなWebアプリを、Dockerコンテナ上に移植する

## 概要
以前Qiitaで公開した、[Vue.jsとAWSでつくるシングルページの家計簿アプリ](https://qiita.com/KMim/items/f3975e308d07df4b359f)を、Dockerコンテナ上に移植しました。そこで、その過程をここに軽くまとめておきます。

[GitHubのリポジトリは、こちらです。](https://github.com/KMimura/AccountingApp)`docker-compose`コマンドが使えるサーバーにコードをデプロイすれば、使えるようになると思います。

```bash:インストールのコマンド
# コードをGitHubから取得
git clone https://github.com/KMimura/AccountingApp
# 取得されたフォルダに移動
cd AccountingApp
# docker-composeの立ち上げ
docker-compose up -d
```

## 元のサーバーレスのアプリ

同アプリは、元々サーバーレスな感じでデプロイしてありました。
![tmp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/283226/11bb6be4-9f01-4921-008d-dc28f2f52259.png)

Vue.jsで作ったフロントエンドの部分はS3上に静的ホスティングをされていて、各種データは、Lambda上で動くPythonで書かれたAPIプログラムを通して、RDSに格納されていました(LambdaとRDSの[組み合わせは悪い](https://qiita.com/asahi0301/items/49808ccf16dc2fff06f3)ですが、自分一人しかアクセスしないアプリだったので、問題はありませんでした)。

これらを、**`1. S3上にデプロイされてたフロントエンド部分`**、**`2. Lambda上にデプロイされてたAPI部分`**、**`3. RDS上にデプロイされてたDB部分`**の3つのコンテナに分けて、`docker-compose`で動かせるようにしました。
## Docker移行の詳細
### 1. フロントエンド用コンテナ
元々はS3上に静的ホスティングされていた部分のコンテナです。以下がDockerfileです（ファイル全体は[こちら](https://github.com/KMimura/AccountingApp/tree/master/website)）。

```docker
FROM nginx:alpine

COPY ./nginx/website.conf /etc/nginx/conf.d
RUN rm /etc/nginx/conf.d/default.conf
WORKDIR mkdir -p /var/www/accounting
COPY ./website /var/www/accounting
```
nginxの設定ファイルを`COPY`していますが、内容は以下のようになっています。

```nginx
server {
    listen *;
    server_name ~^.*$;

    location /accounting/ {
        root /var/www/;
    }

    location /accounting-api {
        proxy_pass http://apiserver:8080/accounting-api;
    }
}
```

`/accounting/`にリクエストが来たら静的フロントエンドファイルにアクセスさせて、`/accounting-api/`にリクエストが来たら、後述のAPIコンテナにアクセスさせます。

ちなみにこのアプリはアクセス制限元のIPアドレスを制限してあるのですが、サーバーレスの時には[s3のポリシー設定](https://qiita.com/flat-8-kiki/items/5392d931d8e34a4e2ae4)でそれを行っていましたが、移行後は[nginxの設定](https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-tcp/)でそれを行うようにしました。
### 2. API用コンテナ
元々はLambda上にデプロイされていたAPIです。以前はAPI Gatewayが、Pythonのプログラムが動くLambda関数にリクエストを振り分けていたのですが、それをGo言語の[Ginというフレームワーク](https://github.com/gin-gonic/gin)を用いたものに変えました。

API部分のプログラムの一部を抜粋すると、以下のような感じです。

```go
func main() {
	// ~ 省略~
	r := gin.Default()
	r.GET("/accounting-api/", func(c *gin.Context) {
		log.Println(c.Request.URL.Host)
		log.Println(c.Request.URL.Path)
		results := getMethod(c, env)
		// ~ 省略~
		c.JSON(200, result)
	})
	r.POST("/accounting-api", func(c *gin.Context) {
		log.Println(c.Request.URL.Host)
		log.Println(c.Request.URL.Path)
		res := postMethod(c, env)
		status := 200
		message := "Success"
		if !res {
			status = 500
			message = "Internal Server Error"
		}
		c.JSON(status, gin.H{
			"state": message,
		})
	})
	r.DELETE("/accounting-api", func(c *gin.Context) {
		log.Println(c.Request.URL.Host)
		log.Println(c.Request.URL.Path)
		res := deleteMethod(c, env)
		status := 200
		message := "Success"
		if !res {
			status = 500
			message = "Internal Server Error"
		}
		c.JSON(status, gin.H{
			"state": message,
		})
	})
	log.Println("Start Server")
	r.Run(":8080")
}
```
リクエストのメソッドに応じて呼び出す関数を変えているということが、雰囲気からわかるかと思います（コード全体は[こちら](https://github.com/KMimura/AccountingApp/blob/master/api/api/main.go)）。

このプログラムが動くコンテナの`Dockerfile`が、以下です。

```docker
FROM golang:alpine
RUN apk update && apk add --no-cache git
RUN go get -u github.com/gin-gonic/gin && go get -u github.com/jinzhu/gorm && go get -u github.com/go-sql-driver/mysql
RUN mkdir /usr/src && mkdir /usr/src/api
COPY ./api /usr/src/api
WORKDIR /usr/src/api
CMD ["go","run","main.go"]
```

特に凝ったことはせず、素直に動かしてます。余談ですが、Ginサーバーを動かす際に出たエラーに関して以前戸惑ったので、記事にしてあります。

- [Dockerコンテナ上で動くGinサーバーにアクセスできないエラーの解決法](https://qiita.com/KMim/items/c31e01f4dee81a2e949a)

### 3. DB用コンテナ
大したことはしてないので、`Dockerfile`は作ってません。このコンテナについては`docker-compose.yml`で定義してあります。

Windowsの環境で動かすときにエラーが起こったので、その内容を以前記事にしました。

- [DockerのMySQLコンテナが、Docker Toolbox上で起動に失敗するエラーの対処法](https://qiita.com/KMim/items/b432ad9fb39f509c2d84)

### docker-compose.yml
3つのコンテナをまとめて動かす`docker-compose.yml`は、以下のような設定になっています。

```yml
version: '3.3'
services:
  web:
    build: ./website/
    ports: 
      - '80:80'
  db:
    image: mysql:5.7
    volumes: 
      - "/tmp/db/data:/var/lib/mysql"
    restart: always
    hostname: db
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-test}
      MYSQL_DATABASE: ${MYSQL_DATABASE:-test}
      MYSQL_USER: ${MYSQL_USER:-test}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-test}
      TZ: 'Asia/Tokyo'
    container_name: db
    ports:
      - 3306:3306
    command: --innodb-use-native-aio=0
  api:
    depends_on:
      - db
    build: ./api/
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE:-test}
      MYSQL_USER: ${MYSQL_USER:-test}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-test}
    container_name: api

```
`web`は`api`に、`api`は`db`にそれぞれアクセスするようになっているのですが、そのための正しい設定を模索するのに時間がかかりました。最終的には、上のような感じでうまくいくようになりました。

環境変数系はすべてデフォルト値が`test`となっています。`git pull`してきたディレクトリに`.env`というファイルを作ってその中に変数を書き込むことで、値は変えられます。

## まとめ
とりあえず元々動いていたものを丸っとDockerに持ってくることができました。今後は同アプリに認証機能とかをつけてみたいと思います（Ginサーバーで認証機能をつけている例があんまりないので、いろいろ大変そう）。