そうだ、Dockerを覚えよう(検証ログ)

検証ログ。これを見てなにかの作業を行わないこと(手戻りも多々あるログです)。

# 覚えること(取り掛かる前の疑問)

- `dockerfile`, `docker-compose`ってなんだ
- `docker image`を開発メンバーで共有するためには？(`docker hub`？をローカルで作れる？)
- `docker-compose`と`Pacemaker`の絡み方
- `Node.js`のAppをコンテナ化する時に`Image`にファイルを入れるのか、`Volume`で常にホストをマウントするのか(前者だと思ってるけどネタがなさすぎる)

# `dockerfile`, `docker-compose`ってなんだ

Docker周りで用語が多すぎる。ざっくり理解する。

参考URL集
- [[Django] Dockerファイルとdocker-compose.ymlの違い](https://zenn.dev/mom/articles/519466e9c7a22d)
- [Dockerfile と docker-compose を利用すると何がうれしいのか？](https://qiita.com/sugurutakahashi12345/items/0b1ceb92c9240aacca02)

|用語|内容|
|---|---|
|dockerfile|1つのコンテナのレシピ|
|docker-compose.yml|複数のコンテナを組み合わせたアプリのレシピ|
|docker Engine|docker操作用のUI。CLIベース。Ubuntuでやるならだいたいこっち|
|docker Desktop|docker操作用のUI。GUIベース。Windows, Macはこっちだった。仮想マシンを作って操作する|

# `docker-compose`と`Pacemaker`の絡み方

参考URL集
- [ocf_heartbeat_docker-compose](https://manpages.debian.org/testing/resource-agents/ocf_heartbeat_docker-compose.7.en.html)

なんかありそう。`docker-compose`ベースでRA制御できんのかな。
でも毎回ビルドとかされちゃ敵わないな。

# とりあえずやってみよう

## dockerのインストール

参考URL集
- [簡単な4つの方法】UbuntuにDockerをインストールするには](https://kinsta.com/jp/blog/install-docker-ubuntu/)
- [Ubuntu 22.04にdockerをインストールする](https://qiita.com/yoshiyasu1111/items/17d9d928ceebb1f1d26d)

インストールする方法は3種あるらしい。

1. `apt`でいい感じに最新の物を入手
1. `.deb`を取得しておいてローカルインストール
1. 自動インストールスクリプト

正直3つ目はなんのこっちゃ。
構成管理マンとしては「2.」が好きだけど一旦「1.」で試しておく。

まず`apt`リポジトリを最新化し、`docker`関連の`apt`リポジトリを入手するために必要なパッケージを取得

```
# aptリポジトリを最新化
$ sudo apt update

# docker関連のaptリポジトリを追加するために必要なパッケージを取得
$ sudo apt install ca-certificates curl gnupg lsb-release
```

DockerのGPGの鍵をaptに登録する。
参考：[GPGとは](https://qiita.com/y518gaku/items/435838097c700bbe6d1b#gpg%E3%81%A8%E3%81%AF)

```
$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ sudo chmod a+r /etc/apt/keyrings/docker.gpg
```

`chmod`に`a+r`ってなんだ？って思ったけど「all + r」のことか。普段`chmod +x`とか略しちゃっているけど明記すると`a+x`になるんだな。

```
$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

`tee`ってなんだっけ。
> teeコマンドとは？ 「tee」は標準入力から受け取った内容を、標準出力とファイルに書き出すコマンドです。 ファイルへの保存と標準出力への出力を同時に行ったり、複数のファイルに出力したりすることができます。

いや、じゃあ`/dev/null`に出すなら`tee`じゃなくていいじゃん…駄目？

```
$ sudo apt update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

参考にしている2つのサイトで最後に上記で差分が出た。`docker-compose-plugin`の有無。これずっと昔に一瞬`docker`触った時に覚えてる。ゆくゆく必要になるから先に入れておいたほうが良いって。

と思ったけど、指定しなくても入ってた。他のいずれかと一緒にインストールされてたみたい。

```
$ docker -v
Docker version 24.0.6, build ed223bc
```

## 一般ユーザでもdockerを実行できるようにする

```
$ getent group | grep docker
docker:x:999:
$ sudo usermod -aG docker $USER
$ getent group | grep docker
docker:x:999:udon
```

一旦ログインし直して`id`コマンドで所属を確認する。

```
$ id
uid=1000(udon) gid=1000(udon) groups=1000(udon),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd),999(docker)
```

動作確認

```
$ docker run hello-world


Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

## dockerfile書いてみる

`Node.js`をコンテナで起動するお作法を学ぶ。

参考URL集
- [DockerでNode.jsを動かすときのベストプラクティス](https://blog.shinonome.io/nodejs-docker/)
- [Dockerイメージを軽量化しよう](https://blog.shinonome.io/lighter-docker-image/)

例題通りに脳みそ殺してバッドプラクティスを倣ってみる

```js:index.js
const express = require('express')
const app = express()
const port = 3000

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})
```

```json:package.json
{
  "name": "nodejs-docker",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "node index.js"
  },
  "author": "conop",
  "license": "MIT",
  "dependencies": {
    "express": "^4.18.1"
  }
}
```

```
$ ls -ltR ../opt
../opt:
total 8
-rw-rw-r-- 1 udon udon 214 Sep 18 14:42 pakcage.json
-rw-rw-r-- 1 udon udon 219 Sep 18 14:41 index.js
```

```dockerfile:Dockerfile
FROM node
WORKDIR /app
COPY . .
RUN yarn install
CMD "yarn" "start"
```

```
$ docker build -t nodejs-docker .
:
[+] Building 55.9s (9/9) FINISHED            docker:default
:
docker run -p 3000:3000 nodejs-docker
>> yarn run v1.22.18
>> $ node index.js
>> sample app listening on port 3000

$ docker image ls nodejs-docker
REPOSITORY      TAG       IMAGE ID       CREATED          SIZE
nodejs-docker   latest    cce892b92beb   17 minutes ago   1.1GB
```

## node.jsのバージョンは？

https://nodejs.org/ja
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259373/00baa13d-0c3f-48e9-b47d-cc8d46c986bb.png)

LTSの最新は`18.17.1`。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259373/7817cea4-f779-a0da-4e47-ada6bc048a7c.png)

2025-04-30までサポート。

[docker HUB](https://hub.docker.com/_/node/)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/259373/2d7a0864-a4ab-54f6-6e2e-00201480d2aa.png)

`slim`がついているのは以下
- 18.17.1-bookworm-slim
- 18.17.1-slim
- 18.17.1-bullseye-slim
- 18.17.1-buster-slim

`bookworm`,`bullseye`,`buster`の違いは？

[Docker imageの種類・選び方](https://zenn.dev/ken3pei/articles/1abbf7d974cf5d)

Debianのバージョンらしい。実績の多い`bullseye`を使いたい反面、セキュリティ面でも最新の`bookworm`を選ぶか悩むところ…

```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
WORKDIR /app
COPY . .
RUN yarn install
CMD "yarn" "start"

$ docker build -t nodejs-docker .
$ docker image ls nodejs-docker
REPOSITORY      TAG       IMAGE ID       CREATED         SIZE
nodejs-docker   latest    df64eef74043   2 seconds ago   249MB
```

うぉー！めっちゃ減った。感動。

- [npmとは　yarnとは](https://qiita.com/Hai-dozo/items/90b852ac29b79a7ea02b)
- [DockerfileのRUN命令](https://qiita.com/sky0621/items/d837c566b04464469fdb)
- [Dockerfileリファレンス - RUN](https://docs.docker.jp/engine/reference/builder.html#run)


```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
WORKDIR /app
COPY . .
RUN yarn install --prod --frozen-lockfile
CMD "yarn" "start"
```

- [package-lock.jsonとyarn.lockとは!? 分かりやすくシンプルに解説!!](https://qiita.com/adgjmptwgw/items/cb08aa4a33fecc478422)
- [パッケージマネージャー「Yarn」の使い方](https://www.wakuwakubank.com/posts/307-javascript-yarn/)

```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
WORKDIR /app

COPY package.json ./
RUN yarn install --prod --frozen-lockfile

COPY . .
CMD "yarn" "start"
```

`yarn`でパッケージ管理したことがないので`yarn.lock`が無く、一旦外している。

- [Dockerfileリファレンス - ENTRYPOINT](https://docs.docker.jp/engine/reference/builder.html#entrypoint)
- [DockerfileのCMDとENTRYPOINTの違い](https://zenn.dev/richardimaoka/articles/bd87036acd951e)

`tini`を噛ますことで諸々解決するみたい

```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
WORKDIR /app

RUN apk add --no-cache tini

COPY package.json ./
RUN yarn install --prod --frozen-lockfile

COPY . .

ENTRYPOINT ["/sbin/tini", "--" ]
CMD "node", "index.js"
```

- [Docker 環境別 no cache install](https://qiita.com/hikaruna/items/e049e53b0147e37db977)

```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
ENV NODE_ENV production

WORKDIR /app

RUN apt-get update
RUN apt-get install -y tini
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["/sbin/tini", "--" ]

COPY --chown=node:node package.json ./
RUN yarn install --prod --frozen-lockfile

COPY --chown=node:node . .

USER node
CMD "node", "index.js"

$ docker image ls nodejs-docker
REPOSITORY      TAG       IMAGE ID       CREATED          SIZE
nodejs-docker   latest    ac301270a047   53 seconds ago   270MB
```

```
$ docker run -p 3000:3000 nodejs-docker
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "/sbin/tini": stat /sbin/tini: no such file or directory: unknown.
ERRO[0000] error waiting for container:
```

あれ、`/sbin/tini`が見つからない。
調べたら`/sbin/tini`は`alphin`の場合らしい。我々は`/usr/sbin/tini`でやる。

```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
ENV NODE_ENV production

WORKDIR /app

RUN apt-get update
RUN apt-get install -y tini
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["/usr/bin/tini", "--" ]

COPY --chown=node:node package.json ./
RUN yarn install --prod --frozen-lockfile

COPY --chown=node:node . .

USER node
CMD which tini
CMD "node", "index.js"
```

```
$ docker run -p 3000:3000 nodejs-docker
/bin/sh: 1: node,: not found
```

ﾑｷｰ!!!


```
$ cat Dockerfile
FROM node:18.17.1-bookworm-slim
ENV NODE_ENV production

WORKDIR /app

RUN apt-get update
RUN apt-get install -y tini
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*

ENTRYPOINT ["/usr/bin/tini", "--" ]

COPY --chown=node:node package.json ./
RUN yarn install --prod --frozen-lockfile

COPY --chown=node:node . .

USER node
CMD ["node", "index.js"]
```

最後、`CMD`をJson書きにしてなかったわ。

```
$ docker run -p 3000:3000 nodejs-docker
Example app listening on port 3000
^C $
```