9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

君(Spring bootのバックエンドとTypeScriptのフロントエンド)を(コンテナに)載せて

Posted at

こんにちは。金曜日になるとアニメ映画を観たくなります。Dockerおじさんです。
前回の記事で、Nginxを使ってフロントとバックを同一ポートでアクセスできるようにしました。
今回は、フロントとバックをそれぞれDockerコンテナに載せて起動させようと思います。

環境情報

  • Docker version 20.10.17, build 100c70180f
  • docker-compose version 1.21.2, build a133471
  • nginx version: nginx/1.22.1
  • Node.js v18.14.2 (npm 9.5.0)
  • Java OpenJDK Runtime Environment Microsoft-7209853 (build 17.0.6+10-LTS)

なお、フロント / バックのソースコードは前回の記事と同じものを使用します。

  • フロント: TypeScript + React + MUI
  • バック: Java + Spring boot + gradle

コンテナの構成

今回は2パターンを試してみます。1

  • ①Nginx, フロント, バック 全てが別コンテナに乗っかっているパターン
  • ②Nginxとフロントを1コンテナにまとめており、バックは別コンテナのパターン

ディレクトリ構成

project
.
├── docker-compose.yml
├── front
│   ├── node_modules
│   ├── package.json
│   ├── package-lock.json
│   ├── public
│   ├── README.md
│   ├── src
│   │   ├── App.tsx
│   │   ├── index.tsx
│   │   ├── logo.svg
│   │   ├── react-app-env.d.ts
│   │   ├── reportWebVitals.ts
│   │   └── setupTests.ts
│   └── tsconfig.json
│
├── README.md
│
└── spring
    ├── build.gradle
    ├── gradle
    ├── gradlew
    ├── gradlew.bat
    ├── README.md
    ├── settings.gradle
    └── src
        ├── main
        │   ├── java
        │   │   └── ...
        │   └── resources
        │       └── application.yml
        └── test
            ├── java
            │   └── ...
            └── resources
                └── application.yml

ルートディレクトリにdocker-compose.ymlを置き、その下に
"front" : フロントエンドのソース
"spring" : バックエンドのソース
を持ってきました。

やってみよう

①Nginx, フロント, バック 全てが別コンテナに乗っかっているパターン

コンテナの構成イメージ

下図のように、Nginx, フロント, バックエンドすべてを別のコンテナで動かします。
pattern1.png

Nginxコンテナの準備

nginx.confの記述

ルートディレクトリにnginx/conf/ディレクトリを作成し、以下のようなnginx.confを置きます。

./nginx/conf/nginx.conf
server {
    # Nginxがlocalhost:80でアクセスを受け付けます。
    listen       80;
    server_name  localhost;
    proxy_set_header    Host    $host;
    proxy_set_header    X-Real-IP    $remote_addr;
    proxy_set_header    X-Forwarded-Host       $host;
    proxy_set_header    X-Forwarded-Server    $host;
    proxy_set_header    X-Forwarded-For    $proxy_add_x_forwarded_for;

    # "http://<nginxコンテナ>:80/"配下のアクセスはfrontコンテナの3000番ポートに転送します。
    location / {
        proxy_pass    http://front:3000/;
    }

    # "http://<nginxコンテナ>:80/api/"配下のアクセスはspringコンテナの8080番ポートに転送します。
    location /api/ {
        proxy_pass    http://spring:8080/api/;
    }
}
Dockerfileの準備

以下のようなDockerfileを準備します。

nginx/Dockerfile
FROM nginx:1.22.1

# ベースイメージの設定ファイルを自前に置換
RUN rm /etc/nginx/conf.d/default.conf
COPY ./conf/nginx.conf /etc/nginx/conf.d

CMD ["nginx", "-g", "daemon off;"]

フロントコンテナの準備

frontディレクトリ直下に、以下のようなDockerfileを作成します。

./front/Dockerfile
# ローカルで使っているNode.jsのバージョンと最も近いものを使用
FROM node:18.15.0

# コンテナの作業ディレクトリを/app配下に設定
WORKDIR /app

# /front配下すべてを/app配下に持っていく
COPY ./* /app/
COPY public/ /app/public/
COPY src/ /app/src/

# 必要なモジュールを全てインストール
RUN npm install

# コンテナ起動と同時に"npm start"が走る
ENTRYPOINT [ "npm", "start" ]

これをdocker-composeで走らせます。

フロントコンテナの動作確認

一応、動作確認してみましょう。
①以下のコマンドを実行します。

コマンド
cd <プロジェクトのルートディレクトリ>/front
# コンテナイメージのビルド
docker build -t front .
# "front"というコンテナイメージを"front"という名前で起動
docker run -itd -p 3000:3000 --name front front

②ブラウザでhttp://(docker実行環境のホスト):80/にアクセス
バックエンドがいないので、ローディングアイコンがぐるぐるしていればOKです。
loading.png

バックエンドコンテナの準備

Spring Bootのバックエンドコンテナを準備します。

  • ソースからビルドしたい
  • ビルドされたjarファイル名はプロジェクトごとに可変となるため、ビルド時に指定したい
  • 最終的に実行するコンテナにはjarファイルだけがあればいい

という条件の下、マルチステージビルドを使います。2

以下のようなDockerfileを記述します。

spring/Dockerfile
# ソースのビルド用コンテナ
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu AS builder

# ソースを全て/app配下に持っていく
WORKDIR /app

COPY ./* ./
COPY ./gradle/ ./gradle/
COPY ./src/ ./src/

# sdkman!からgradleを入手→パスを通す
RUN apt-get update
RUN apt-get -y install curl
RUN apt-get -y install zip
RUN curl -s "https://get.sdkman.io" | bash
RUN echo ". $HOME/.sdkman/bin/sdkman-init.sh; sdk install gradle" | bash

# gradle build
RUN $HOME/.sdkman/candidates/gradle/current/bin/gradle build

# 実際に動かすコンテナの記述
FROM mcr.microsoft.com/openjdk/jdk:17-ubuntu

# gradle buildで生成される実行可能jarファイルのパス ファイル名をbuild-argで受け取る
ARG JAR_FILE_PATH="/app/build/libs/demo-0.0.1-SNAPSHOT.jar"

WORKDIR /app

# builderコンテナから実行可能jarファイルをコピー jarファイル名はbuild-argから受け取る
COPY --from=builder ${JAR_FILE_PATH} ./app.jar

ENTRYPOINT ["java", "-jar", "app.jar"]
バックエンドコンテナの動作確認

こちらも一応、動作確認してみましょう。
①以下のコマンドを実行します。

コマンド
cd <プロジェクトのルートディレクトリ>/spring
# コンテナイメージのビルド
# 例: docker build --build-arg JAR_FILE_PATH=<JARファイルのフルパス> -t spring .
# 生成されるjarファイル名が"demo-0.0.1-SNAPSHOT.jar"だと、以下のようになります。
docker build --build-arg JAR_FILE_PATH=/app/build/libs/demo-0.0.1-SNAPSHOT.jar -t spring .
# "spring"というコンテナイメージを"spring"という名前で起動
docker run -itd --name spring spring

②ブラウザでhttp://(docker実行環境のホスト):8080/api/helloにアクセス

curl
curl http://localhost:8080/api/hello
{"jpn":"こんにちは","eng":"hello"}

挨拶文がJSONで返ってくれば成功です。

docker-compose.ymlの作成

最後に、上記3つのコンテナをまとめるdocker-compose.ymlを記述します。

プロジェクトのルートディレクトリに、以下のようなdocker-compose.ymlを作成します。

docker-compose.yml
version: "3"
services:
  nginx:
    build: nginx #nginxディレクトリでコンテナをビルドする
    container_name: nginx
    ports:
      - 80:80 # 80番ポートを内外に公開

  front:
    build: front
    container_name: front
    restart: always
    expose:
      - "3000" # 3000番ポートを内部ネットワークに公開
    depends_on:
      - spring # springコンテナよりも後に起動させる

  spring:
    build:
      context: spring
      args:
        ${JAR_FILE_PATH}: /app/build/libs/demo-0.0.1-SNAPSHOT.jar
    container_name: spring
    restart: always
    expose:
      - "8080" # 8080番ポートを内部ネットワークに公開

.envファイルの作成

docker-compose.yml中の可変の値をすべて.envファイルに集約します。
docker-compose.ymlと同じ階層に以下のような".env"ファイルを作成します。

.env
JAR_FILE_PATH=/app/build/libs/demo-0.0.1-SNAPSHOT.jar

コンテナのビルド

docker-composeコマンドを使用し、コンテナを立ち上げます。

コマンド
cd <プロジェクトのルートディレクトリ>
docker-compose up -d
動作確認

ブラウザで、http://(docker環境のホスト):80/にアクセスします。
success.png
挨拶文が返ってくればOKです。

②Nginxとフロントを1コンテナにまとめており、バックは別コンテナのパターン

コンテナの構成イメージ

下図のような2コンテナの構成です。
pattern2.png

フロントコンテナの準備

nginx.confの準備

front/nginx/conf配下に以下のようなnginx.confを作成します。

server {

  listen 80;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
    try_files $uri $uri/ /index.html;
  }

  location /api/ {
      proxy_pass http://spring:8080;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection 'upgrade';
      proxy_set_header Host $host;
      proxy_cache_bypass $http_upgrade;
   }

  error_page   500 502 503 504  /50x.html;

  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}
Dockerfileの準備

マルチステージビルドを用いて、フロントコンテナを準備します。3

front/Dockerfile
# ローカルで使っているNode.jsのバージョンと最も近いものを使用
FROM node:18.15.0 AS builder

# コンテナの作業ディレクトリを/app配下に設定
WORKDIR /app

# /front配下すべてを/app配下に持っていく
COPY ./* /app/
COPY public/ /app/public/
COPY src/ /app/src/

# 必要なモジュールを全てインストール
RUN npm install

# フロントのソースをビルド
RUN npm run build

# 実際に動かすコンテナ
FROM nginx:1.22.1

# ベースイメージの設定ファイルを自前に置換
RUN rm /etc/nginx/conf.d/default.conf
COPY ./nginx/conf/nginx.conf /etc/nginx/conf.d

# ビルドされたフロントのソースを持っていく
COPY --from=builder /app/build/ /usr/share/nginx/html

CMD ["nginx", "-g", "daemon off;"]

バックエンドコンテナの準備

バックエンドコンテナはパターン①と同じです。

docker-compose.ymlの作成

最後に、上記3つのコンテナをまとめるdocker-compose.ymlを記述します。

プロジェクトのルートディレクトリに、以下のようなdocker-compose.ymlを作成します。

docker-compose.yml
version: "3"
services:
  front:
    build: front # frontディレクトリでコンテナをビルドする
    container_name: front
    restart: always
    ports:
      - 80:80 # 80番ポートを内外に公開
    depends_on:
      - spring # springコンテナよりも後に起動させる

  spring:
    build:
      context: spring
      args:
        - ${JAR_FILE_PATH}: /app/build/libs/demo-0.0.1-SNAPSHOT.jar
    container_name: spring
    restart: always
    expose:
      - "8080" # 8080番ポートを内部ネットワークに公開

.envファイルの作成

docker-compose.yml中の可変の値をすべて.envファイルに集約します。
docker-compose.ymlと同じ階層に以下のような".env"ファイルを作成します。

.env
JAR_FILE_PATH=/app/build/libs/demo-0.0.1-SNAPSHOT.jar

コンテナのビルド

docker-composeコマンドを使用し、コンテナを立ち上げます。

コマンド
cd <プロジェクトのルートディレクトリ>
docker-compose up -d
動作確認

ブラウザで、http://(docker環境のホスト):80/にアクセスします。
success.png
挨拶文が返ってくればOKです。

  1. 全てを1コンテナにまとめることもできますが、1コンテナに3アプリが入ることになり、
    障害に弱いのであまり実用的でない という観点からやらないことにしました。
    この辺はいろんな派閥があると思いますが、悪しからず。。

  2. 実業務のCI/CD環境では、
    ①ソースがメインにマージされる
    ②ビルドツールが検知してjarファイル生成
    ③docker buildでは、それをコピーして起動するだけ のようなやり方が現実的かと考えます。

  3. フロントにおいても、やはり実業務のCI/CD環境では、
    ①ソースがメインにマージされる
    ②ビルドツールが検知してnpm run build
    ③docker build では、②で出来上がったbuildディレクトリをコピーして起動するだけ が現実的かと考えます。

9
7
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
9
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?