0
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アプリを動かしてみる

0
Posted at

はじめに

今回はDockerのお勉強がてら、複数コンテナを立てて簡単なアプリケーションを動かしてみました。
実際にどんなことをしたのか書いていこうと思います。

今回作ったもの

  • Apache2を利用したWebコンテナ
  • Node.js + Expressを利用したAPコンテナ
  • MySQLを利用したDBコンテナ

それぞれのコンテナをつなげて簡単なアプリケーションを動かしました。
以下はファイル構成になります。

greet-app-docker
│   .env
│   docker-compose.yml
│
├───api
│       Dockerfile
│       greeting.js
│       package.json
│       package-lock.json
│
├───db
│       Dockerfile
│       init.sql
│
└───web
    │   Dockerfile
    │
    └───src
            greet.js
            index.html

それぞれの作業を詳しく

Webコンテナ作成

1. Dockerfile作成

webディレクトリを作成し、まずはDockerfileを作成します。

Dockerfile
FROM ubuntu
RUN apt update
RUN apt install -y apache2
COPY ./src/  /var/www/html/
CMD ["apache2ctl", "-D", "FOREGROUND"]

Ubuntuイメージを使用し、Apache2をインストールします。
画面表示に使うindex.htmlと、ボタン操作で発火するJavaScript(greet.js)はsrcディレクトリにまとめて置いているので、srcディレクトリをApache2の公開ディレクトリである/var/www/html/にコピーします。
最後にApache2をフォアグラウンドモードで起動してDockerfileの記述は完了です。

2. index.htmlを作成

srcディレクトリにindex.htmlを作成し、アプリの画面を実装します。

index.html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>挨拶アプリ</title>
  </head>
  <body>
    <h2>挨拶アプリ</h2>
    <label for="greet-select">言語を選択してください</label>
    <select id="greet-select">
      <option value="ja">日本語</option>
      <option value="en">英語</option>
      <option value="ch">中国語</option>
    </select>
    <br />
    <button
      id="greeting"
      onclick="greet(document.getElementById('greet-select').value)"
    >
      挨拶する
    </button>
    <div id="message"></div>
    <script src="greet.js"></script>
  </body>
</html>

機能はシンプルで、プルダウンリストで言語を選択し、「挨拶する」ボタンを押すことで次に作成するgreet関数が発火し、戻り値を画面表示するだけです。

3. greet.jsを作成

index.html同様にsrcディレクトリにgreet.jsを作成します。

greet.js
const greet = async (lang) => {
  const response = await fetch(
    `http://localhost:8080/?lang=${encodeURIComponent(lang)}`,
  );
  const result = await response.json();
  document.getElementById('message').innerHTML = `<h2>${result[0].word}</h2>`;
};

これはAPIをフェッチしてレスポンスのwordをHTMLファイルのID = messageにinnerHTMLする関数を記述しています。
後述するAPコンテナは8080番ポートで待ち受けているため、ブラウザからはlocalhost:8080宛てにfetchすることでAPIを呼び出せます。アプリを作り終えた後に記事を書いているため、先にfetch先を書いていますが、実体は次のAPコンテナ作成で作るものになります。

APコンテナ作成

1. Dockerfile作成

apiディレクトリを作成し、Dockerfileを作成します。

Dockerfile
FROM node
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "node", "greeting.js" ]

Node.jsイメージを使用し、/usr/src/appディレクトリを作成します。
次にpackage.jsonとpackage-lock.jsonをコピーし、npm installでライブラリをインストールします。
ポートに8080を指定し、Node.jsサーバーを起動してDockerfileの記述は終了です。

2. greeting.jsを作成

greeting.jsファイルを作成し、画面から引数を受け取り、DBコンテナから対象のデータを取得・画面に返すAPIを作成します。

greeting.js
const express = require('express');
const cors = require('cors');
const mysql = require('mysql2');
const app = express();
app.use(cors());
app.get('/', (req, res) => {
  const connection = mysql.createConnection({
    host: process.env.DB_HOST || 'db',
    user: 'mysql',
    password: 'mysql',
    database: 'mysql_study',
    charset: 'utf8mb4',
  });
  connection.connect();
  connection.query(
    'SELECT * FROM greetings WHERE lang = ?',
    [req.query.lang],
    (error, results) => {
      if (error) {
        res.status(500).json({ error: error.message });
        return;
      }
      res.status(200).json(results);
    },
  );
});
const PORT = 8080;
const HOST = '0.0.0.0';
app.listen(PORT, HOST);
console.log(`Running on http://${HOST}:${PORT}`);

greeting.jsでは以下のライブラリを使用します。

  • express:Node.js上でAPIサーバーを構築するためのフレームワーク。app.get('/', ...)のようにルーティングを定義したり、res.status(200).json(results)のようにレスポンスを返したりする処理を簡潔に書けるようにするために使用しています。
  • cors:Webコンテナ(localhost:80)から配信されるindex.htmlは、APコンテナ(localhost:8080)に対してfetchでアクセスします。ポート番号が異なるとブラウザからは別オリジンとみなされ、そのままではCORS制約によりリクエストがブロックされてしまいます。これを回避し、異なるオリジンからのアクセスを許可するために使用しています。
  • mysql2:DBコンテナで稼働しているMySQLに接続し、SQLクエリを実行してデータを取得するために使用しています。

DBコンテナ作成

1. Dockerfile作成

dbディレクトリを作成し、Dockerfileを作成します。

Dockerfile
FROM mysql
COPY ./init.sql /docker-entrypoint-initdb.d/
EXPOSE 3306

MySQLイメージを使用し、後述するinit.sqlファイルを/docker-entrypoint-initdb.d/にコピーします。このディレクトリに置かれたSQLファイルは、MySQL公式イメージの仕組みにより、データディレクトリが空の状態でコンテナが初回起動した際に自動的に実行されます。

2. init.sql作成

テーブルの定義をするためにinit.sqlファイルを作成します。

init.sql
SET NAMES utf8mb4;
CREATE TABLE IF NOT EXISTS greetings (
  id INT AUTO_INCREMENT PRIMARY KEY,
  lang VARCHAR(2) NOT NULL,
  word VARCHAR(100) NOT NULL
);
INSERT INTO greetings (lang, word) VALUES ('ja', 'こんにちは');
INSERT INTO greetings (lang, word) VALUES ('en', 'Hello');
INSERT INTO greetings (lang, word) VALUES ('ch', '你好');

コンテナ作成時にlang列とword列を持ったgreetingsテーブルを作成し、初期データを投入するための処理を記載しています。

.env作成

dbコンテナ作成時にMySQLのルートパスワードやデータベース、ユーザーを自動作成できるようにするために必要な情報を.envファイルに記載していきます(テーブル自体は前述のinit.sqlで作成されます)。

.env
MYSQL_ROOT_PASSWORD=mysql
MYSQL_DATABASE=mysql_study
MYSQL_USER=mysql
MYSQL_PASSWORD=mysql

ルートユーザーのパスワード、データベース名、ユーザー、パスワードを記載します。

docker-compose.yml作成

作成した3つのコンテナを起動するためのdocker-compose.ymlファイルを作成します。

docker-compose.yml
services:
  db:
    build: ./db
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    ports:
      - '3306:3306'
    healthcheck:
      test: ['CMD', 'mysqladmin', 'ping', '-h', 'localhost']
      interval: 5s
      timeout: 3s
      retries: 10
  web:
    build: ./web
    ports:
      - '80:80'
    depends_on:
      - api
  api:
    build: ./api
    ports:
      - '8080:8080'
    environment:
      DB_HOST: db
    depends_on:
      db:
        condition: service_healthy

dbエリア

.envファイルで記載した変数を使用してenvironmentを記載します。
ポートは3306をマッピングします。

webエリア

ポートは80をマッピングします。
また、APコンテナが起動していないと画面からのfetchが失敗してしまうため、depends_onでAPコンテナ→Webコンテナの順番で起動されるようにしています。

apiエリア

ポートは8080をマッピングします。
また、DB_HOSTはdbコンテナを指定し、healthcheckが通ってから(DBが実際に接続を受け付けられる状態になってから)APコンテナが起動するようにしています。

動作確認

コマンドプロンプトで以下のコマンドを入力して、各コンテナを起動します。

コマンドプロンプト
docker compose up

起動が確認出来たらlocalhost:80に行ってみます。

image.png

Webコンテナのindex.htmlで作成した画面が表示されています。
言語を選択して挨拶するボタンを押してみます。

image.png

Helloが返ってきました。他の言語でもDBコンテナで設定したものが返ってきます。
これで動作確認は終了です。

最後に

Dockerfileやリソースを1から作るのは今回が初めてだったのですが、書き方さえわかってしまえばとても簡単にコンテナ作成から起動まで行うことができました。
以前WSL2を使って仮想環境に同じようなアプリを作成したのですが、Dockerを使用したほうが簡単だったなと思いました。

0
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
0
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?