1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Dockerを使用して、Web三層構造を実現する

Last updated at Posted at 2024-05-30

概要

Web, APP, DBの3つのコンテナを作成し、それぞれのコンテナにWeb三層構造のそれぞれの責務に分割する。
Nginx ⇄ APP ⇄ DB

背景

この構成に変更する目的は、以下の二つ

  1. 元の構成では、app.jarに静的リソースを含める必要があった
  2. 負荷分散を実現したい

それぞれについて具体的に見ていく。

1. 元の構成では、app.jarに静的リソースを含める必要があった

元々は、SpringBootのThymeleafでフロントエンド部分を作成していた。
しかし、SpringBootのフロントエンド部分をThymeleafで実現した場合、Javaのビルド後の成果物(app.jar)に静的ファイルが含まれてしまうことになる。

この場合、フロントエンドのソースを変更した場合にも、app.jarを作り直す必要があり、ホットリロードが非効率になってしまう。

フロントエンドのソースの変更後の具体的な手順

  1. Vue, TypeScriptのファイルをJavaScriptにトランスパイルし、適切なディレクトリに配置する
  2. アプリケーションのビルドを行い、app.jarを作成する

フロントエンドのソースのみを変更した場合には、フロントエンドのトランスパイルのみで済ませたい。

2. 負荷分散を実現したい

元の構成の場合には、フロントエンドのソースもapp.jarに含まれている。
そのため、フロントエンドとバックエンドとでサーバーを分割することができず、単純な画面描画などの場合にも同じサーバーへのアクセスが必要になってしまう。

Web三層構造を実現する

まずは、最終的な成果物を示す。
Web三層構造.png

point

  1. Webブラウザからのリクエストは、Nginxコンテナが受け付ける
  2. 必要であれば、APPやDBコンテナへのリクエストが行われる
  3. フロントエンドのソースは、Node.jsコンテナ(仮名)でトランスパイルし、Nginxコンテナに配置する

対応手順

  1. Node.jsのコンテナを作成する
  2. Nginxコンテナを作成し、ソースを配置する
  3. APPコンテナとの連携を図る
  4. DBコンテナとの連携を図る
  5. CORS問題を対応する

APP ⇄ DBについては、以下で対応している。

Node.jsのコンテナを作成する

Node.jsのコンテナの責務は、フロントエンドのソースをトランスパイルして、Nginxコンテナに配置すること。
フロントエンドのビルドは、Viteを使用している。

本番環境と開発環境とで、若干の挙動の違いがあるので、整理しておく。

本番環境
コンテナ内でフロントエンドのソースをトランスパイルし、Nginxコンテナとリソースを共有する。
トランスパイル後、コンテナは停止する。

開発環境
コンテナ内でフロントエンドのソースをトランスパイルし、Nginxコンテナとリソースを共有する。
ソースの変更に応じて、ホットデプロイを行うため、コンテナは起動し続ける。

Dockerfile

Node.jsのDockerfileを示す。

FROM node:21.7.1-bullseye-slim
WORKDIR /app

# キャッシュ機能を有効活用するためにパッケージ関連のものを先にCOPYしている。
COPY yarn.lock package.json .yarnrc.yml .
COPY .yarn/ ./.yarn/
RUN yarn install

COPY . .
CMD yarn run ${NODE_ENV}

ベースイメージ
Node.jsの公式イメージを使用する。

  • 本番環境のサーバーとして利用するには向いていない
  • ディストリビューションは、bullseye(Debian 11)

キャッシュの有効化
キャッシュの機能を有効に使うために、依存ファイルとソースファイルとでCOPYの位置を変えている。
依存ファイルの変更は稀なので、通常時(ソースのみを修正した場合)には、6行目より前はキャッシュが利用される。

CMDから実行されるスクリプト
package.jsonは、以下のようになっている。

  • 開発環境の場合はWatchモードでビルドを実行している
  • 本番環境の場合は、ソースのビルドだけを実施し、スクリプトの実行が完了し次第、コンテナが停止する
{
  "scripts": {
    "dev": "vite build --watch --mode dev",
    "production": "vue-tsc --noEmit && vite build --mode production",
  },
}

docker-compose.yml

Node.jsのコンテナのdocker-compose.ymlを示す。

web:
    container_name: web_container
    build: ./web
    volumes:
      - type: bind
        source: ./web/src
        target: /app/src
    environment:
      NODE_ENV: ${ENVIRONMENT}

バインドマウント
コンテナ上に、フロントエンドのソースをバインドマウントしている。
開発サーバーでは、ここで最新のフロントエンドのソースを検知し、フロントエンドのリビルドを実施する。

Nginxコンテナを作成し、リソースを配置する

Nginxコンテナの責務は、Node.jsのコンテナからビルド済みのリソースを受け取り、配信すること。

Dockerfile

NginxのDockerfileを示す。(Nginxについてはあまり深掘りしていないので、設定内容も軽め...)

FROM nginx:latest
COPY ./default.conf /etc/nginx/conf.d/default.conf

docker-compose.yml

Nginxのdocker-compose.ymlを示す。

 web:
    container_name: web_container
    build: ./web
    volumes:
      - type: bind
        source: ./web/src
        target: /app/src
      - type: volume # 追加
        source: web-source
        target: /app/dist
    environment:
      NODE_ENV: ${ENVIRONMENT}
  nginx:
    container_name: nginx_container
    build: ./nginx
    volumes:
      - type: volume
        source: web-source
        target: /app
    ports:
      - 3000:80
    depends_on: # Nginxコンテナが先に起動しても問題ないため、depends_onとしている。
      - web
volumes:
  web-source:

ボリュームマウント
NginxとNode.jsのコンテナ間でのビルド済みのフロントエンドのソースの共有は、ボリュームマウントで行なっている。
そのため、Node.jsのコンテナでビルドしたリソースは、Nginxコンテナから参照できる。

コンテナの起動順
一応、Node.jsコンテナ、Nginxコンテナの順で起動するように設定している。
(Nginxコンテナでは、リソースが配置されるまでは404となるだけなので、あまり重要ではないが...)

APPコンテナとの連携を図る

Axiosを使用して、フロントエンドからバックエンドへのリクエストを可能にする。
APPコンテナの設定については、こちらを参照。(TODO)

以下のように、RestControllerのパスにリクエストを投げるだけ。

onMounted(() => {
  axios
    .get('/sample') // エンドポイントにリクエストを投げる。
    .then((response: AxiosResponse) => {
      console.log(response);
    })
    .catch((err: AxiosError) => {
      console.log(err);
    });
});

CORS問題を対応する

CORSとは、ブラウザが別のオリジンに対してJavaScriptによるリクエストを送信した場合に、そのリクエストをブロックするかどうかを設定するためのもの。(詳細

特に設定をしない場合、Axiosによるリクエスト時に以下のエラーとなった。

Access to XMLHttpRequest at 'http://localhost:8080/sample' from origin 'http://localhost' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

API側(SpringBoot)とNginx側のそれぞれで対応する必要がある。

  1. Webブラウザは、プリフライトリクエストを送信する(method: OPTIONS
  2. レスポンスヘッダーからCORSの設定を確認し、元のリクエストを送信するかどうかを決める

SpringBootでCORS設定を行う

CORSは、API側を保護するためのものであり、アクセス許可についてはAPI側で設定を行う。
リクエスト元では、この許可しているCORS設定に適合するようなリクエストを送る必要がある。

こちらを参考にした。

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

  /**
   * CORSの設定を許可する。
   */
  @Override
  public void addCorsMappings(CorsRegistry registry) {
    registry.addMapping("/api/**").allowedOrigins("http://localhost:3000")
        .allowedMethods("GET", "POST", "PUT", "DELETE").allowedHeaders("*");
  }
}

NginxでCORSを対応する

Nginxの設定ファイル(default.conf)に以下を追加した。
/apiへのリクエストの場合には、APPサーバーへのリクエストを中継している。

location /api/ {
    proxy_pass http://localhost:8080;
}

参考文献

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?