Dockerでnode.js + PostgreSQLを動かす


諸事情でDocker上のnode.jsとPosgreSQLを連携させる必要があったので調査。

## 方針

最終的にクラウド上のマネージドサービス（Cloud Run想定）でコンテナを動かす予定なのでdocker-composeへの依存は可能な限り下げます。

* 書ける記述はなるべくDockerfileに記述する
* 全体制御をdocker-composeに記述する

>環境はDocker Desktop for Macを利用。

## 準備

### 作業場の確保

```bash
cd
mkdir docker-node-pg-test
cd docker-node-pg-test
mkdir app db
```
## PostgreSQL(db)関連の作業

appはdbに依存するので、先にdbの準備をします。

### dbに移動

```bash
cd db
```

### ディレクトリ作成


```bash
mkdir data init
```

### 必要なファイル作成

```bash
touch Dockerfile .dockerignore init/init.sql
```

### init/init.sqlの記述


```sql:init.sql
create table if not exists members(
    id serial primary key,
    name text,
    age int
);

insert into members(name,age) values('hoge',10);
```

### Dockerfileの記述

```bash
FROM postgres:14.5-bullseye

COPY ./init/init.sql /docker-entrypoint-initdb.d/init.sql

ENV POSTGRES_USER postgres
ENV POSTGRES_PASSWORD postgres

EXPOSE 5432
```

### イメージのビルド

```bash
docker build -t my_postgres .
```

### 動作確認

#### 起動

```bash
docker run --name my_db -p 5432:5432 -d my_postgres
```

#### ログイン

```bash
psql -h localhost -U postgres -d postgres
```

#### データ確認

```bash
postgres=# select * from members;

 id | name | age
----+------+-----
  1 | hoge |  10
(1 row)
```

どうやら動いているようです。

>この状態ではデータは永続化されていないので、コンテナを削除するとデータも消えます（停止では消えません）。



## node.js(app)の開発作業

では次にPostgreSQLにアクセスするnode appを作成したいと思います。

### appに移動

```bash
cd ../app
```

### 必要なファイルの生成

```bash
touch index.js
```

### モジュールのインストール

```bash
npm install express pg
```


### 実装


```js:index.js
const express = require("express");
const { send } = require("express/lib/response");
const app = express();

const pg = require("pg");

// PostgreSQL connect info
const pool = new pg.Pool({
    user: "postgres",
    host: "localhost",
    database: "postgres",
    password: "postgres",
    port: 5432
});

// /
app.get("/", (req, res) => {
    res.send("Hello Node.js");
});

// /members
app.get("/members", (req, res) => {

    pool.connect((err, client) => {
        if (err) {
            res.send(err.stack);
        } else {
            const query = {
                text: "select * from members",
                values: [],
            };
            client.query(query, (error, response) => {
                if (error) {
                    res.send(error.stack);
                } else {
                    res.json(response.rows);
                }
            })
        }
    });

})

app.listen(3000, () => {
    console.log("Start server on port 3000.");
});
```

### 動作確認

```bash
node index.js
```

http://localhost:3000/
http://localhost:3000/members

```bash
[{"id":1,"name":"hoge","age":10}]
```

>PostgreSQLにアクセスするには先述のmy_postgresが動作している必要があります。

## node.js(app)のDocker Image化

### 必要なファイルの生成

```bash
touch Dockerfile .dockerignore
```

### Dockerfile

```bash
FROM node:18-bullseye-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node","index.js"]
```

### .dockerignore

```bash
node_modules
npm-debug.log
```

### イメージのビルド

```bash
docker build -t my_node .
```

### 実行

```bash
docker run --name my_app -p 3000:3000 -d my_node
```

### 動作確認（エラーと対応）

* http://localhost:3000/
* http://localhost:3000/members


/は問題ないが、/membersはPostgreSQLの接続エラーが発生する。


```bash
Error: connect ECONNREFUSED 127.0.0.1:5432 at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1300:16)
```

### エラーの原因と対応


#### 原因

localhostはコンテナ自信を指すので（ここではmy_app）当然、PostgreSQLは動いていない。
localhostではなくコンテナ名を指定する必要がある。また、コンテナ間で通信を行うには同じネットワークに所属してる必要があるみたい。


#### 対応

以下の２つの対応が必要。

* localhostではなく、コンテナ名（my_db)でhostを指定
* 接続するコンテナを同じネットワークに所属させる

>docker-composeを利用している場合はサービス名orコンテナ名でいけるみたい。


#### localhostではなくコンテナ名でホストを指定

```js:抜粋
// PostgreSQL connect info
const pool = new pg.Pool({
    user: "postgres",
    host: "my_db", //ココを変更
    database: "postgres",
    password: "postgres",
    port: 5432
});
```

#### 同じネットワークでコンテナを起動


```bash
docker network create test-network
```

>docker network lsで一覧が確認できる。

```bash
docker run --name my_db --network test-network -p 5432:5432 -d my_postgres
docker run --name my_app --network test-network -p 3000:3000 -d my_node
```

### 動作確認（再）

* http://localhost:3000/
* http://localhost:3000/members

```bash
[{"id":1,"name":"hoge","age":10}]
```

## docker-compose化する

```bash
cd
cd docker-node-pg-test

touch docker-compose.yml
```

```yml:docker-compose.yml
version: '3'

services:
  app:
    build: ./app
    container_name: my_app
    ports:
      - 3000:3000
    networks:
      - backend
    depends_on:
      - db
  db:
    build: ./db
    container_name: my_db
    ports:
      - 5432:5432
    networks:
      - backend

networks:
  backend:
```

```bash
docker-compose up -d
```

```bash
docker-compose down
```

## Kubernetesで動かす

長くなりそうなので[こちら](https://qiita.com/zaburo/private/1beb52139b00f94265e4)に分けました。