Help us understand the problem. What is going on with this article?

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

概要

以前Qiitaで公開した、Vue.jsとAWSでつくるシングルページの家計簿アプリを、Dockerコンテナ上に移植しました。そこで、その過程をここに軽くまとめておきます。

GitHubのリポジトリは、こちらです。docker-composeコマンドが使えるサーバーにコードをデプロイすれば、使えるようになると思います。

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

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

同アプリは、元々サーバーレスな感じでデプロイしてありました。
tmp.png

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

これらを、1. S3上にデプロイされてたフロントエンド部分2. Lambda上にデプロイされてたAPI部分3. RDS上にデプロイされてたDB部分の3つのコンテナに分けて、docker-composeで動かせるようにしました。

Docker移行の詳細

1. フロントエンド用コンテナ

元々はS3上に静的ホスティングされていた部分のコンテナです。以下がDockerfileです(ファイル全体はこちら)。

FROM nginx

COPY ./nginx/website.conf /etc/nginx/conf.d
RUN rm /etc/nginx/conf.d/default.conf
RUN mkdir -p /var/www/accounting
ADD ./website /var/www/accounting

nginxの設定ファイルをCOPYしていますが、内容は以下のようになっています。

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のポリシー設定でそれを行っていましたが、移行後はnginxの設定でそれを行うようにしました。

2. API用コンテナ

元々はLambda上にデプロイされていたAPIです。以前はAPI Gatewayが、Pythonのプログラムが動くLambda関数にリクエストを振り分けていたのですが、それをGo言語のGinというフレームワークを用いたものに変えました。

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

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")
}

リクエストのメソッドに応じて呼び出す関数を変えているということが、雰囲気からわかるかと思います(コード全体はこちら)。

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

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サーバーを動かす際に出たエラーに関して以前戸惑ったので、記事にしてあります。

3. DB用コンテナ

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

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

docker-compose.yml

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

version: '3.3'
services:
  web:
    build: ./website/
    ports: 
      - '80:80'
    links:
      - "api:apiserver"
  db:
    image: mysql:5.7
    volumes: 
      - "/tmp/db/data:/var/lib/mysql"
      - "./db:/usr/src/db"
    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'
    ports:
      - 3306:3306
    command: --innodb-use-native-aio=0
  api:
    depends_on:
      - db
    build: ./api/
    links:
      - "db:database"
    environment:
      MYSQL_DATABASE: ${MYSQL_DATABASE:-test}
      MYSQL_USER: ${MYSQL_USER:-test}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD:-test}

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

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

まとめ

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

KMim
文系出身社会人3年目です。
https://github.com/KMimura
fnlp
金融データ処理や自然言語処理に興味のあるメンバーがあつまって情報交換するコミュニティです
https://github.com/fnlp-group
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした