Ubuntu パソコン起動時にDockerコンテナを自動で稼働させる

#### Linux系ディスクトップパソコンで起動時にDockerコンテナを起動する

普段使っている開発用のパソコンには複数のDockerコンテナが有りますが、すべてのDockerコンテナが常時稼働しているわけではなく、必要になった Docker コンテナのみをその都度起動するようにしています。

以下は開発用パソコンにセットアップされている すべての docker コンテナ

```bash
docker
├── influxdb
│   ├── trial
│   │   ├── Dockerfile
│   │   ├── docker-compose.yml
│   │   ├── schema
│   │   │   └── weather-sensor.json
│   │   └── scripts
│   │       └── create_bucket_schema.sh
│   └── v2
│       ├── docker-compose.yml
│       ├── influxv2.env
│       └── telegraf
│           └── mytelegraf.conf
├── mysql
│   ├── Dockerfile
│   ├── config
│   │   ├── conf.d
│   │   │   └── docker.cnf
│   │   └── my.cnf
│   ├── docker-compose.yml
│   └── initdb
│       └── create_mylearn.sql
└── postgresql
    ├── 12
    │   ├── Dockerfile
    │   ├── docker-compose.yml
    │   └── initdb
    │       ├── 01_create_weather.sh
    │       ├── 10_createdb_sensors.sql
    │       └── 11_weather_db.sql
    ├── 16
    │   ├── Dockerfile
    │   ├── docker-compose.yml
    │   └── initdb
    │       ├── 10_createdb.sql
    │       └── 11_createtable.sql
    └── qiita
        ├── Dockerfile
        ├── docker-compose.yml
        └── initdb
            └── 10_createdb.sql
```

例えば Qiita 投稿用のデータベースコンテナは投稿記事の執筆で必要になったきだけ起動しています。

```bash
$ cd ~/docker/postgresql/qiita
$ docker compose up -d
[+] Running 2/2
 ✔ Network qiita_default     Created                           0.5s 
 ✔ Container postgres-qiita  Started
```

ただ開発用のパソコンでも常時稼働していてほしい Docker コンテナは有ります。
自分は自宅サーバーを稼働させているので不正アクセスしてきたホストの情報を毎日 PostgreSQL サーバー (Dockerコンテナ) に記録しているのですが、このコンテナはパソコン起動時に自動で稼働してほしいコンテナになります。

https://qiita.com/pipito-yukio/items/147f7b2f42ad9fc1dbd0

**この記事では最新のUbuntu 24.04 (デスクトップ) に 最新(安定版)の Docker Engine (Community edition) をインストールし、 端末起動時に PostgreSQL サーバー (docker コンテナ) を稼働させるスクリプトを紹介します。スクリプト化することで、新しく導入する端末にも同一環境を構築することが可能になります。**

さすがに開発用PCで検証するのは危険な作業なので今回はインストール用端末としてラズパイ４を使った例を示します。ラズパイ４を使うと OS は SDカードにインストールできるため手軽に検証することができます。


### 参考サイト

Docker エンジンインストール 公式ドキュメント (Ubuntu)
[Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
上記ドキュメントの手順にしたがって Docker Engine をインストールします。

### 参考書

**CentOS 7 システム管理ガイド systemd / NetworkManager Firewalld徹底攻略**  
【発行日】2015年11月 1日 第1版第1刷  
【ISBN】978-4-7980-4491-0 C3055  
【発行所】株式会社　秀和システム  
 
![Books_CentOS7.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/2885b9b0-795f-ea5e-dea2-737f865765a1.jpeg)
【参照にした箇所】
**[Chapter 1] systemd [1.3.4] systemd-notify を使う**


### 1 環境

#### 1-1 検証用端末

+ ラズパイ4
  Raspberry Pi 4 ModelB 4GB

![Raspi_dev_overview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/7c4e252e-c7cb-28c7-8f4f-83b57821c188.jpeg)
+ OS: Ubuntu 24.04 (デスクトップ)
    + OSのインストール作業はモニターとキーボード・マウスを接続
    *  漢字ディレクトリ名をアルファベットに変更
**```$ LANG=C;xdg-user-dirs-gtk-update```**
    + OSインストール後に Upgrade 実行
    + SSHサーバーのインストール (openssh-server)
※OSインストール完了後は 開発PCからSSHでログインして作業
    + Ipv4 ネットワーク: Static IP アドレスに変更
    ※以降下記の 
    ホスト名: raspi-4-dev, ユーザ: raspi, IPアドレス: 192.168.0.20 
<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/083df9fd-3694-cb7c-96e6-990d4d587ed1.png">

OSバージョン

```bash
raspi@raspi-4-dev:~$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 24.04.1 LTS
Release:	24.04
Codename:	noble
```

アーキテクチャ (arm64)

```bash
raspi@raspi-4-dev:~$ hostnamectl
 Static hostname: raspi-4-dev
       Icon name: computer
      Machine ID: 888eca6205914adaa91dff4876a07a63
         Boot ID: 2375063679c94ecebb900d19ccce5a55
Operating System: Ubuntu 24.04.1 LTS              
          Kernel: Linux 6.8.0-1013-raspi
    Architecture: arm64
```

開発PCからSSH経由でスクリプトを実行するため起動モードを **multi-user.target** に変更
※起動モードが GUI のままだとモニタ、キーボード・マウスが必要になるので面倒です
```bash
# OSインストール直後
raspi@raspi-4-dev:~$ systemctl get-default
graphical.target
# 起動モード変更
raspi@raspi-4-dev:~$ sudo systemctl set-default multi-user.target
Removed "/etc/systemd/system/default.target".
Created symlink /etc/systemd/system/default.target → /usr/lib/systemd/system/multi-user.target.
# 変更の確認
raspi@raspi-4-dev:~$ systemctl get-default
multi-user.target
```

#### 1-2 開発用PCの環境

+ OS: Ubuntu 22.04  (デスクトップ)

```bash
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 22.04.5 LTS
Release:	22.04
Codename:	jammy
```

##### 1-2-1 SDカードのバックアップ

Upgrade実行後に SD カードの内容を開発用 PC にバックアップし、dockerコンテナ生成の失敗時にOS状態を戻せるようにします。

SDカードを開発用 PC を挿したときのデバイスを確認

```bash
$ LANG=C;lsblk -e7 | grep sdc
sdc                       8:32   1  59.5G  0 disk 
├─sdc1                    8:33   1   512M  0 part /media/xxxxx/system-boot
└─sdc2                    8:34   1    59G  0 part /media/xxxxx/writable
```

SDカードの内容を開発用PCにバックアップ

```bash
$ sudo dd bs=8M if=/dev/sdc \
 | gzip > UbuntuDesktop24_04_raspi-4-dev_upgraded.img.gz
[sudo] password for xxxxx: 
3730+1 records in
3730+1 records out
31293702144 bytes (31 GB, 29 GiB) copied, 728.261 s, 43.0 MB/s
```

##### 1-2-2 バックアップからのリストア

(1) SDカードのパーティションを確認する

```bash
$ LANG=C;sudo fdisk -l /dev/sdc
[sudo] password for xxxxx: 
Disk /dev/sdc: 29.14 GiB, 31293702144 bytes, 61120512 sectors
Disk model: MassStorageClass
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xa0e2a9f8

Device     Boot   Start      End  Sectors  Size Id Type
/dev/sdc1  *       2048  1050623  1048576  512M  c W95 FAT32 (LBA)
/dev/sdc2       1050624 61120478 60069855 28.6G 83 Linux
```

(2) SDカードのアンマウント

```bash
$ sudo umount /dev/sdc1
$ sudo umount /dev/sdc2
```

(3) パーティションの削除

```bash
$ sudo fdisk /dev/sdc

Welcome to fdisk (util-linux 2.37.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): d
Partition number (1,2, default 2): 

Partition 2 has been deleted.

Command (m for help): d
Selected partition 1
Partition 1 has been deleted.

Command (m for help): w
The partition table has been altered.
Calling ioctl() to re-read partition table.
Syncing disks.
```

(4) バックアップのリストア

```bash
$ gunzip --stdout UbuntuDesktop24_04_raspi-4-dev_upgraded.img.gz \
 | sudo dd bs=8M of=/dev/sdc
0+850944 records in
0+850944 records out
31293702144 bytes (31 GB, 29 GiB) copied, 1757.42 s, 17.8 MB/s
```

### 2 Docker コンテナ作成

PostgreSQL-16 データベースコンテナを作成します。

#### 2-1 Docker Engine インストール

SSHでラズパイ４にログインし、コマンドラインから実行します。

```bash
$ ssh raspi@192.168.0.20
raspi@192.168.0.20's password: 
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-1013-raspi aarch64)
# ...一部省略...
Last login: Thu Nov  7 17:34:27 2024 from 192.168.0.101
```

参考サイトの公式ドキュメントページの手順にしたがって実行します。

(1) 非公式パッケージの削除

Raspberry Pi の Ubuntu 24.04 には docker パッケージはインストールされていませんが手順にしたがって実行します。

+ 非公式パッケージ
    + docker.io
    + docker-doc
    + docker-compose
    + docker-compose-v2
    + podman-docker
    + containerd
    + runc

※1 出力される日本語メッセージは煩雑なので一時的に C ロケールに変更します。
※2 未インストールの場合はその旨のメッセージが出力されるだけです。

```bash
raspi@raspi-4-dev:~$ LANG=C
raspi@raspi-4-dev:~$ for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get -y remove $pkg; done
[sudo] password for raspi: 
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Package 'docker.io' is not installed, so not removed
0 upgraded, 0 newly installed, 0 to remove and 6 not upgraded.
# ...以下省略...
```

(2) リポジトリ情報更新

```bash
raspi@raspi-4-dev:~$ sudo apt-get update
```

(3) dockerパッケージ取得用ライブラリイントール

```bash
raspi@raspi-4-dev:~$ sudo apt-get install -y ca-certificates curl
```

(4) 公式 GPG キーを keyringsディレクトリに保存

```bash
raspi@raspi-4-dev:~$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
raspi@raspi-4-dev:~$ sudo chmod a+r /etc/apt/keyrings/docker.asc 
```
※保存されたファイルの内容の抜粋は以下のとおりです。
```bash
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBFit2ioBEADhWpZ8/wvZ6hUTiXOwQHXMAlaFHcPH9hAtr4F1y2+OYdbtMuth
lqqwp028AqyY+PRfVMtSYMbjuQuu5byyKR01BbqYhuS3jtqQmljZ/bJvXqnmiVXh
...一部省略...
jCxcpDzNmXpWQHEtHU7649OXHP7UeNST1mCUCH5qdank0V1iejF6/CfTFU4MfcrG
YT90qFF93M3v01BbxP+EIY2/9tiIPbrd
=0YYh
-----END PGP PUBLIC KEY BLOCK-----
```

(5) Dockerリポジトリ情報を Apt sources.list に追加してリポジトリ情報を更新

```bash
raspi@raspi-4-dev:~$ echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
 sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
raspi@raspi-4-dev:~$ sudo apt-get update
```

※保存されたファイルの内容は以下のとおりです。
```bash
deb [arch=arm64 signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu  noble stable
```

(6) 公式 Docker パッケージインストール

+ docker-ce
Docker Community Edition ※無償版
+ docker-ce-cli
docker-ce のコマンドラインツール
+ containerd.io
Container runtime ※コンテナを実行するために構築されたランタイム
+ docker-buildx-plugin
Docker コマンドを拡張する CLI プラグイン
+ docker-compose-plugin
Docker compose プラグイン

```bash
raspi@raspi-4-dev:~$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io \
 docker-buildx-plugin docker-compose-plugin
```

docker関連サービス (containerd.service, docker.service, docker.socket) が構成されます。

```bash
# ...途中の出力省略...
Created symlink /etc/systemd/system/multi-user.target.wants/containerd.service -> /usr/lib/systemd/system/containerd.service.
# ...途中の出力省略...
Created symlink /etc/systemd/system/multi-user.target.wants/docker.service -> /usr/lib/systemd/system/docker.service.
Created symlink /etc/systemd/system/sockets.target.wants/docker.socket -> /usr/lib/systemd/system/docker.socket.
```

(7) バージョン確認

```bash
raspi@raspi-4-dev:~$ docker --version
Docker version 27.3.1, build ce12230
raspi@raspi-4-dev:~$ docker compose version
Docker Compose version v2.29.7
```

(8) ログインユーザーを docker グループに追加して実行権限を付与
※実行権限の付与を有効にするには一旦ログアウトする必要が有ります。
```bash
raspi@raspi-4-dev:~$ sudo gpasswd -a $USER docker
Adding user raspi to group docker
raspi@raspi-4-dev:~$ exit
logout
Connection to 192.168.0.20 closed.
```

#### 2-2 PostgreSQL データベースコンテナの作成

PostgreSQL-16 データベース用のコンテナ生成・起動に docker compose を利用します。
※コンテナ生成と起動をファイルで作成可能のためメンテナンスが容易になります。

##### 2-2-1 データベースコンテナ生成に必要なリソースファイル一覧

```
├── data
│   └── sql                           # DDL SQLファイル等の格納ディレクトリ (空)
├── docker                            # docker compose 用リソースディレクトリ
│   └── postgres
│       ├── .env                      # 機種ごとの差を埋める環境設定ファイル
│       ├── Dockerfile
│       ├── docker-compose.yml
│       └── initdb
│           ├── 10_createdb.sql      　# データベース作成DDL
│           └── 11_createtables.sql  　# テーブル生成DDL
└── postgresql                       　# PostgreSQL データベース格納ディレクトリ (空)
```

docker compose 用リソースの詳細については下記投稿記事をご覧ください。

https://qiita.com/pipito-yukio/items/5a335f11ca97fa1ee4a7

(1) Dockerfile

PostgreSQLの Dokcer 公式イメージ
[(dockerHub) Official Images / postgres](https://hub.docker.com/_/postgres)
※軽量の alpine のイメージを選択

```docker
FROM postgres:16-alpine

COPY initdb/*.sql /docker-entrypoint-initdb.d/
```

(2) docker compose 設定ファイル

ホスト側のボリュームを環境変数として環境変数ファイルに定義します。
※インストール端末に関わらずこの設定ファイルは変更せずにすみます。

```docker-compose.yml
services:
  postgres:
    build: .
    env_file: ./.env
    container_name: $CONTAINER_NAME
    ports:
      - "5432:5432"
    volumes:
      - "${HOST_PG_VOLUME}/data:/var/lib/postgresql/data"
      - "${HOST_HOME}/data/sql:${CONTAINER_HOME}/data/sql"
    environment:
      - POSTGRES_USER=${PG_USER}
      - POSTGRES_PASSWORD=${PG_PASSWD}
```

(3) 端末依存の環境変数ファイル

※以下はラズパイにインストールすることを前提とした値を設定しています。

```.env
# Qiita投稿用データベース
CONTAINER_NAME=postgres-raspi
# ラズパイ端末のユーザディレクトリ
USER=raspi
HOST_HOME="/home/$USER"
HOST_PG_VOLUME="$HOST_HOME/postgresql"
PG_USER=postgres
PG_PASSWD=xxxxxxxxx

# display locale
LANG=C

# locale
LANGUAGE=ja_JP:ja
LC_ALL=ja_JP.UTF-8
# Time zone
TZ=Asia/Tokyo

# volume mount
CONTAINER_HOME=/home/qiita
```

参考までに開発用PC の環境値を下記に示します。
※データベース格納ディレクトリは NFSマウントした NAS 上のボリュームを指定

```開発用PC/.env
#CONTAINER_NAME=postgres-16
# SynologyNAS: /volume1/Databases/postgres-16
# Host Nfs: /mnt/nas_databases <- SynologyNAS:/volume1/Databases
HOST_PG_VOLUME=/mnt/nas_databases/postgres-16
DB_NAME=mydmain_pgdb
...一部省略...
# volume mount
HOST_HOME=/home/loginuser
CONTAINER_HOME=/home/loginuser
```

(4) データベース生成DDLファイル

コンテナビルド時にデータベースとテーブルを生成します。

```initdb/10_createdb.sql
CREATE ROLE developer WITH LOGIN PASSWORD 'xxxxxxx';
ALTER ROLE developer WITH SUPERUSER;
CREATE DATABASE qiita_exampledb WITH OWNER=developer ENCODING='UTF-8' LC_COLLATE='ja_JP.UTF-8' LC_CTYPE='ja_JP.UTF-8' TEMPLATE=template0;
GRANT ALL PRIVILEGES ON DATABASE qiita_exampledb TO developer;
```

先頭行の **```\connect qiita_exampledb```** は必須です。
※これを忘れると postgres データベースにテーブルが作成されてしまいます。

```initdb/11_createtables.sql
\connect qiita_exampledb

-- Qiita投稿用テスト用スキーマ
CREATE SCHEMA mainte2;

-- 不正アクセスされたIPアドレス管理テーブル
CREATE TABLE mainte2.unauth_ip_addr(
   id INTEGER NOT NULL,
   ip_addr VARCHAR(15) NOT NULL,
   reg_date DATE NOT NULL,
   country_code CHAR(2)
);
CREATE SEQUENCE mainte2.ip_addr_id OWNED BY mainte2.unauth_ip_addr.id;
ALTER TABLE mainte2.unauth_ip_addr ALTER id SET DEFAULT nextval('mainte2.ip_addr_id');
ALTER TABLE mainte2.unauth_ip_addr ADD CONSTRAINT pk_unauth_ip_addr PRIMARY KEY (id);
-- IPアドレスは重複なし
CREATE UNIQUE INDEX idx_ip_addr ON mainte2.unauth_ip_addr(ip_addr);

-- 不正アクセスカウンターテープル
CREATE TABLE mainte2.ssh_auth_error(
   log_date date NOT NULL,
   ip_id INTEGER,
   appear_count INTEGER NOT NULL
);
ALTER TABLE mainte2.ssh_auth_error ADD CONSTRAINT pk_ssh_auth_error
   PRIMARY KEY (log_date, ip_id);
ALTER TABLE mainte2.ssh_auth_error ADD CONSTRAINT fk_ssh_auth_error
   FOREIGN KEY (ip_id) REFERENCES mainte2.unauth_ip_addr (id);

ALTER SCHEMA mainte2 OWNER TO developer;
ALTER TABLE mainte2.unauth_ip_addr OWNER TO developer;
ALTER TABLE mainte2.ssh_auth_error OWNER TO developer;
```

##### 2-2-2 データベースコンテナ生成のビルド

開発用PCからラズパイ４にリソースファイルをコピーします。

```bash
$ scp -r data docker postgresql raspi@192.168.0.20:~
raspi@192.168.0.20's password: 
```

ラズパイ４にSSHログインしてリソースを確認します。

```bash
raspi@raspi-4-dev:~$ ls -lrt --time-style long-iso | grep -v total
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Downloads
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Desktop
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Videos
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Templates
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Public
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Music
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Documents
drwx------ 3 raspi raspi 4096 2024-11-06 10:16 snap
drwxr-xr-x 4 raspi raspi 4096 2024-11-06 11:15 Pictures
drwxrwxr-x 3 raspi raspi 4096 2024-11-12 18:09 data
drwxrwxr-x 3 raspi raspi 4096 2024-11-12 18:09 docker
drwxrwxr-x 2 raspi raspi 4096 2024-11-12 18:09 postgresql
drwxrwxr-x 9 raspi raspi 4096 2024-11-12 18:09 work
```

(1) ログインユーザにdocker実行権限が付与されているか確認

末尾に **```xxx(docker)```** が追加されていればOK

```bash
raspi@raspi-4-dev:~$ id                 
uid=1002(raspi) gid=1002(raspi) groups=1002(raspi),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),113(lpadmin),123(sambashare),984(docker)
```

(2) リソースファイルの確認

```bash
raspi@raspi-4-dev:~$ cd docker/postgres/
raspi@raspi-4-dev:~/docker/postgres$ ls -l --time-style long-iso | grep -v total
-rw-rw-r-- 1 raspi raspi  332 2024-11-12 18:09 .env
-rw-rw-r-- 1 raspi raspi   72 2024-11-12 18:09 Dockerfile
-rw-rw-r-- 1 raspi raspi  341 2024-11-12 18:09 docker-compose.yml
drwxrwxr-x 2 raspi raspi 4096 2024-11-12 18:09 initdb
```

(3) コンテナのビルド

```bash
raspi@raspi-4-dev:~/docker/postgres$ docker compose up --build -d
[+] Building 52.2s (8/8) FINISHED                                                                             docker:default
 => [postgres internal] load build definition from Dockerfile                                                           0.3s
 => => transferring dockerfile: 109B                                                                                    0.0s
 => [postgres internal] load metadata for docker.io/library/postgres:16-alpine                                          3.2s
 => [postgres internal] load .dockerignore                                                                              0.3s
 => => transferring context: 2B                                                                                         0.0s
 => [postgres internal] load build context                                                                              0.6s
 => => transferring context: 1.63kB                                                                                     0.0s
 => [postgres 1/2] FROM docker.io/library/postgres:16-alpine@sha256:2aa615bd7f5c50fb2373411963caa48a66a938b29eebd0721  29.4s
 => => resolve docker.io/library/postgres:16-alpine@sha256:2aa615bd7f5c50fb2373411963caa48a66a938b29eebd0721eef29b1aee  0.5s
 => => sha256:2aa615bd7f5c50fb2373411963caa48a66a938b29eebd0721eef29b1aee4bb28 10.29kB / 10.29kB                        0.0s
# ...途中のログ割愛...
 => [postgres 2/2] COPY initdb/*.sql /docker-entrypoint-initdb.d/                                                      17.7s
 => [postgres] exporting to image                                                                                       0.6s
 => => exporting layers                                                                                                 0.3s
 => => writing image sha256:033aaf39f99521f55dd09ca5178ee41daca2c4ce05d8c7477e7eaf1844dca91c                            0.0s
 => => naming to docker.io/library/postgres-postgres                                                                    0.1s
 => [postgres] resolving provenance for metadata file                                                                   0.0s
[+] Running 2/2
 ✔ Network postgres_default  Created                                                                                    0.3s 
 ✔ Container postgres-raspi  Started                                                                                    1.4s 
```

(4) コンテナの起動確認

```bash
raspi@raspi-4-dev:~/docker/postgres$ docker container ls
CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS         PORTS                                       NAMES
0252fbabfd92   postgres-postgres   "docker-entrypoint.s…"   2 minutes ago   Up 2 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   postgres-raspi
```

(5) データベースとテーブルの確認

コンテナのビルド実行でデータベースが作成されるとともにデータベース上にテーブルが作成されました。

```bash
raspi@raspi-4-dev:~$ docker exec -it postgres-raspi bash
fbc6aa8c0427:/# psql -Udeveloper qiita_exampledb
psql (16.4)
Type "help" for help.

qiita_exampledb=# SET search_path TO public,mainte2;
SET
qiita_exampledb=# \dn
       List of schemas
  Name   |       Owner       
---------+-------------------
 mainte2 | developer
 public  | pg_database_owner
(2 rows)

qiita_exampledb=# \dt
              List of relations
 Schema  |      Name      | Type  |   Owner   
---------+----------------+-------+-----------
 mainte2 | ssh_auth_error | table | developer
 mainte2 | unauth_ip_addr | table | developer
(2 rows)

qiita_exampledb=# \dS unauth_ip_addr
                                Table "mainte2.unauth_ip_addr"
    Column    |         Type          | Collation | Nullable |             Default             
--------------+-----------------------+-----------+----------+---------------------------------
 id           | integer               |           | not null | nextval('ip_addr_id'::regclass)
 ip_addr      | character varying(15) |           | not null | 
 reg_date     | date                  |           | not null | 
 country_code | character(2)          |           |          | 
Indexes:
    "pk_unauth_ip_addr" PRIMARY KEY, btree (id)
    "idx_ip_addr" UNIQUE, btree (ip_addr)
Referenced by:
    TABLE "ssh_auth_error" CONSTRAINT "fk_ssh_auth_error" FOREIGN KEY (ip_id) REFERENCES unauth_ip_addr(id)

qiita_exampledb=# \q
fbc6aa8c0427:/# exit
exit
```

#### 2-3 データベースコンテナ起動のシステムサービス化

端末起動時にデータベースコンテナを稼働させるためにはシステムサービスに登録する必要か有ります。

workディレクトリのユニットファイルをシステムディレクトリにコピーし有効化します。

```
├── bin
│   └── postgres-docker-service.sh    # コンテナの起動・削除 実行スクリプト
└── work
    └── etc
        ├── default
        │   └── postgres-docker     # ユニットファイルの環境変数設定ファイル
        └── systemd
            └── system              # カスタムサービスのユニットファイル
                └── postgres-docker.service         # コンテナ起動・削除
```


(1) docker コンテナを起動、または削除するシェルスクリプト

コンテナの起動完了後 **```systemd-notify --ready```** で、 **Type=notify** が指定されているサービスに処理完了を通知します。


```bin/postgres-docker-service.sh
#!/bin/bash

docker_cmd=$(which docker)

case "$1" in
  start)
    echo "docker-directory: $2"
    cd $2 # Directory in docker-compose.yml
    $docker_cmd compose up -d
    sleep 1
    # コンテナの起動が完了したら Type=nofity が指定されているサービスに通知する
    systemd-notify --ready
    echo "PostgreSQL($2) container ready!"
    sleep 1
    container_ls=`docker container ls`
    echo "$container_ls"
    cd ~
    ;;
  stop)
    # At shutdown
    exists_container=$(${docker_cmd} container ls | grep postgres-raspi)
    if [ -n "$exists_container" ]; then
       cd $2
       $docker_cmd compose down
       echo "PostgreSQL($2) container down!"
       cd ~
    else
       echo "docker.service already shutdownded."
    fi
    ;;
  *)
    exit 1
    ;; 
esac
```

(2) 環境変数設定ファイル

docker compose 設定ファイルの格納ディレクトリを指定

```work/etc/defalt/postgres-16-docker
BUILD_PATH=/home/raspi/docker/postgres
```

(3) データベースコンテナ起動サービス

サービスタイプに **notify** を設定することで、このサービスの稼働完了まで他のサービスの起動を待たせることが可能になります。

```work/etc/systemd/system/postgres-16-docker.service
[Unit]
Description=PostgreSQL-16 Start service with docker
After=docker.service

[Service]
Type=notify
TimeoutStartSec=0
NotifyAccess=all
EnvironmentFile=/etc/default/postgres-16-docker
ExecStart=/bin/sh -c "/home/raspi/bin/postgres-docker-service.sh start $BUILD_PATH"
User=raspi

[Install]
WantedBy=multi-user.target
```

:::note info
Type=**notify** 指定と シェルスクリプト **```systemd-notify```** を使う理由は?
:::

**docker コンテナを起動する処理は時間がかかる処理のため、データベースを必要とするアプリサービスがこのサービス開始のあとに実行されるように構成されていてもデータベースが立ち上がらないうちにアプリサービスが先に起動してしまうことがあります。**

このような事象が発生するシステムの例を以下に示します。

![Raspi-4_03_services.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/bd23bf75-b362-2ef4-80fc-9a5ebe9f20db.png)

上記のような複数のサービスを設定して端末(ラズパイ４)を起動すると以下のようなエラーログが出力され２つのアプリサービスともに起動に失敗します。

データベース接続エラー ※ログの先頭 (タイムスタンプ) 列は省略しています
**could not connect to server: Network is unreachable**

```
udp_monitor_from_weather_sensor.sh[470]:     self.conn = psycopg2.connect(**dbconf)
udp_monitor_from_weather_sensor.sh[470]:   File "/home/pi/py_venv/raspi4_apps/lib/python3.9/site-packages/psycopg2/__init__.py", >
udp_monitor_from_weather_sensor.sh[470]:     conn = _connect(dsn, connection_factory=connection_factory, **kwasync)
udp_monitor_from_weather_sensor.sh[470]: psycopg2.OperationalError: could not connect to server: Network is unreachable
udp_monitor_from_weather_sensor.sh[470]:         Is the server running on host "raspi-4.local" (192.168.0.16) and accepting
udp_monitor_from_weather_sensor.sh[470]:         TCP/IP connections on port 5432?
udp_monitor_from_weather_sensor.sh[470]: could not connect to server: Network is unreachable
udp_monitor_from_weather_sensor.sh[470]:         Is the server running on host "raspi-4.local" (192.168.0.16) and accepting
udp_monitor_from_weather_sensor.sh[470]:         TCP/IP connections on port 5432?
```

**２つのアプリケーションサービスがデータベースを参照しているため、２つのアプリケーションサービスは PostgreSQLコンテナ起動完了を待って起動されなければなりません。**

**しかし下記のようにそれぞれのサービスユニットで前後関係を指定しても、順番通りに起動されますがデータベースコンテナの稼働が完了する前に後続のアプリケーションサービスが起動されてしまいます。**
![Services_01_order_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/04cffaba-d7e9-c051-322a-30ffb2613021.png)

そこで PostgreSQLコンテナ起動サービスユニットの **```Type=notify```** とシェルスクリプトの **```system-notify --ready```** を連携させることにより、PostgreSQLコンテナの起動完了後にアプリケーションサービスを起動することができます。

![Services_02_order_OK.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3583120/8da5c2f3-0944-e5d7-9ad8-880b55d3989d.png)

実際のシステムについては下記 GitHubで公開しています。
[UDP Weather Sensor packet monitor for Raspberry pi 4](https://github.com/pipito-yukio/raspi4_apps)

３つのサービスユニットのソースは下記でご覧になれます。
[raspi4_apps /src/installer](https://github.com/pipito-yukio/raspi4_apps/tree/master/src/installer)


##### 2-2-2 ユニットファイルをシステムサービスに登録

開発用PCからラズパイ４にリソースファイルをコピーします。

```bash
$ scp -r bin work raspi@192.168.0.20:~
raspi@192.168.0.20's password: 
postgres-docker-service.sh                         100%  695    74.6KB/s   00:00  
postgresql-docker.service                          100%  395    31.7KB/s   00:00  
postgresql-docker                                  100%   39     2.9KB/s   00:00  
```

ラズパイ４にSSHログインしてリソースを確認します。

```bash
raspi@raspi-4-dev:~$ LANG=C;ls -lrt --time-style long-iso | grep -v total
#...2-2-2 と同じなので割愛...
drwxrwxr-x 2 raspi raspi 4096 2024-11-13 18:06 bin
drwxrwxr-x 5 raspi raspi 4096 2024-11-13 18:06 work
```

(1) リソースをシステム領域にコピー

```bash
raspi@raspi-4-dev:~$ sudo cp ~/work/etc/default/postgresql-docker /etc/default
raspi@raspi-4-dev:~$ sudo cp ~/work/etc/systemd/system/postgresql-docker.service /etc/systemd/system
```

(2) ユニットファイルを有効化

```bash
raspi@raspi-4-dev:~$ sudo systemctl enable postgresql-docker.service
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql-docker.service -> /etc/systemd/system/postgresql-docker.service.
```

(3) 端末をリブート

```bash
raspi@raspi-4-dev:~$ sudo reboot
```

(4) ラズパイ４にSSHログインしてサービスの起動状態を確認します。

```bash
raspi@raspi-4-dev:~$ systemctl status postgresql-docker.service 
○ postgresql-docker.service - PostgreSQL container start service
     Loaded: loaded (/etc/systemd/system/postgresql-docker.service; enabled; preset: enabled)
     Active: inactive (dead) since Tue 2024-11-13 18:22:35 JST; 45s ago
   Duration: 1.095s
    Process: 1595 ExecStart=/bin/sh -c /home/raspi/bin/postgres-docker-service.sh start $BUILD_PATH (code=exited, status=0/S>
   Main PID: 1595 (code=exited, status=0/SUCCESS)
        CPU: 818ms

11月 13 18:22:31 raspi-4-dev sh[1616]:  Network postgres_default  Created
11月 13 18:22:31 raspi-4-dev sh[1616]:  Container postgres-raspi  Creating
11月 13 18:22:32 raspi-4-dev sh[1616]:  Container postgres-raspi  Created
11月 13 18:22:32 raspi-4-dev sh[1616]:  Container postgres-raspi  Starting
11月 13 18:22:33 raspi-4-dev sh[1616]:  Container postgres-raspi  Started
11月 13 18:22:34 raspi-4-dev systemd[1]: Started postgresql-docker.service - PostgreSQL container start service.
11月 13 18:22:34 raspi-4-dev sh[1596]: PostgreSQL(/home/raspi/docker/postgres) container ready!
11月 13 18:22:35 raspi-4-dev sh[1596]: CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS   >
11月 13 18:22:35 raspi-4-dev sh[1596]: fbc6aa8c0427   postgres-postgres   "docker-entrypoint.s…"   4 seconds ago   Up 3 seco>
11月 13 18:22:35 raspi-4-dev systemd[1]: postgresql-docker.service: Deactivated successfully.
```

PostgreSQLコンテナが正常起動されました。


### 3 インストールとコンテナビルド作業の自動化

いちいちコマンドラインで入力していては途中で失敗したときにもう一度繰り返さなければならないので大変です。

ここでは 「2 Docker コンテナ作成」で示した手順をスクリプト化します。

+ インストール手順
    + (1) Dockerパッケージのインストールとコンテナのサービス化
    1_inst_libraries.sh
    スクリプト実行後にログアウトする ※リブートはしない
    + (2) PostgreSQLデータベースコンテナのビルド
    2_create_postgresql_container.sh
    スクリプト実行後にリブートする

#### 3-1 インストール用シェルスクリプト作成

(1) Dockerパッケージのインストールとコンテナのサービス化

スクリプト実行前に環境変数 my_passwd を設定する ※ログインユーザのパスワード

```1_inst_libraries.sh
#!/bin/bash

# execute before export my_passwd=xxxxxx

# https://docs.docker.com/engine/install/ubuntu/
#   Install Docker Engine on Ubuntu

# Uninstall old versions
#  最小構成のUbuntu24.04 又はラズパイのUbuntu24.04ではインストールされていない
echo $my_passwd | {
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc
do
   sudo --stdin apt-get -y remove $pkg
done
}

echo $my_passwd | { sudo --stdin apt-get update
   sudo apt-get -y install ca-certificates curl
}

# Dockerの公式GPG鍵を追加する
echo $my_passwd | {
   sudo --stdin curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
   sudo chmod a+r /etc/apt/keyrings/docker.asc
}

# 安定版のリポジトリのセットアップ
echo $my_passwd | { echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo --stdin tee /etc/apt/sources.list.d/docker.list > /dev/null
}

# Docker Engine のインストール: + docker-buildx-plugin 
echo $my_passwd | { sudo --stdin apt-get update
   sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
}

# docker execute to raspi
echo $my_passwd | sudo --stdin gpasswd -a $USER docker

# Enable user apps system services
echo $my_passwd | { sudo --stdin cp ~/work/etc/default/postgresql-docker /etc/default
  sudo cp ~/work/etc/systemd/system/postgresql-docker.service /etc/systemd/system
  sudo systemctl enable postgresql-docker.service
}

echo "Done, logout this terminqal."
```

(2) PostgreSQLデータベースコンテナのビルド

開発用PCからSSHで再ログイン後に実行する。
※ログインユーザに docker実行権限が付与されているので環境変数のパスワード設定は不要

```2_create_postgresql_container.sh
#!/bin/bash

docker_cmd=$(which docker)

# Create PostgreSQL container with qiita_exampledb database.
cd ~/docker/postgres
$docker_cmd compose up --build -d
exit1=$?
echo "Docker create PostgreSQL container >> status=$exit1"
$docker_cmd compose down
if [ $exit1 -ne 0 ]; then
   exit $exit1
fi

cd ~/
echo "Done, sudo reboot now."
```

#### 3-2 インストールアーカイブ作成

開発用PCでリソースの tar アーカイブを作成しインストール端末にコピーします。

```
docker_on_ubuntu_desktop
├── 1_inst_libraries.sh
├── 2_create_postgresql_container.sh
├── bin
│   └── postgres-docker-service.sh
├── data
│   └── sql
├── docker
│   └── postgres
│       ├── .env
│       ├── Dockerfile
│       ├── docker-compose.yml
│       └── initdb
│           ├── 10_createdb.sql
│           └── 11_createtables.sql
├── postgresql
└── work
    └── etc
        ├── default
        │   └── postgresql-docker
        └── systemd
            └── system
                └── postgresql-docker.service
```

(1) tar アーカイブの作成

```bash
$ tar czf ../docker_on_ubuntu_desktop.tar.gz \
 bin data docker postgresql work 1_inst_libraries.sh 2_create_postgresql_container.sh 
```

(2) インストール端末にコピー

```bash
$ scp docker_on_ubuntu_desktop.tar.gz raspi@192.168.0.20:~
raspi@192.168.0.20's password: 
docker_on_ubuntu_desktop.tar.gz                   100% 3296   397.1KB/s   00:00    
```

#### 3-3 インストール用シェルスクリプトの実行

開発用PCからSSHでラズパイ４にログインします。

```bash
$ ssh raspi@192.168.0.20
raspi@192.168.0.20's password: 
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-1013-raspi aarch64)
# ...以下出力省略...
```

ユーザディレクトリ確認

```bash
raspi@raspi-4-dev:~$ ls -lrt --time-style long-iso | grep -v total
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Downloads
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Desktop
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Videos
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Templates
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Public
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Music
drwxr-xr-x 2 raspi raspi 4096 2024-11-06 10:16 Documents
drwx------ 3 raspi raspi 4096 2024-11-06 10:16 snap
drwxr-xr-x 4 raspi raspi 4096 2024-11-06 11:15 Pictures
drwxrwxr-x 5 raspi raspi 4096 2024-11-14 10:06 work
-rw-rw-r-- 1 raspi raspi 3288 2024-11-15 09:58 docker_on_ubuntu_desktop.tar.gz
```


(1) tarアーカイブを解凍

```bash
raspi@raspi-4-dev:~$ tar zxf docker_on_ubuntu_desktop.tar.gz 
raspi@raspi-4-dev:~$ ls -l --time-style long-iso | grep -v 2024-11-06 | grep -v total
-rwxrwxr-x 1 raspi raspi 1829 2024-11-12 11:24 1_inst_libraries.sh
-rwxrwxr-x 1 raspi raspi  332 2024-10-29 09:51 2_create_postgresql_container.sh
drwxrwxr-x 2 raspi raspi 4096 2024-11-12 17:10 bin
drwxrwxr-x 3 raspi raspi 4096 2024-10-21 14:12 data
drwxrwxr-x 3 raspi raspi 4096 2024-10-24 14:00 docker
-rw-rw-r-- 1 raspi raspi 3288 2024-11-15 09:58 docker_on_ubuntu_desktop.tar.gz
drwxrwxr-x 3 raspi raspi 4096 2024-11-15 10:08 postgresql
drwxrwxr-x 5 raspi raspi 4096 2024-11-14 10:06 work
```

##### 3-3-1 Dockerパッケージのインストールとコンテナサービス化

(1) **```1_inst_libraries.sh```** 実行
環境変数にログインパスワードを設定してスクリプト実行
```export my_passwd=xxxxxxx```

最後に下記のようにログが出力されたらインストール成功です。

```bash
raspi@raspi-4-dev:~$ export my_passwd=xxxxxxxx
raspi@raspi-4-dev:~$ ./1_inst_libraries.sh 2>&1 | tee ~/work/logs/1_inst_libraries.log
# ...途中のログ割愛...
Adding user raspi to group docker
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql-docker.service -> /etc/systemd/system/postgresql-docker.service.
Done, logout this terminqal.
```

(2) 一旦端末からログアウト

```bash
raspi@raspi-4-dev:~$ exit
logout
Connection to 192.168.0.20 closed.
```

##### 3-3-2 PostgreSQLデータベースコンテナのビルド

開発用PCから再度ログイン。
ログインユーザにdocker実行権限が付与されているのを確認

```bash
raspi@raspi-4-dev:~$ id
uid=1002(raspi) gid=1002(raspi) groups=1002(raspi),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),113(lpadmin),123(sambashare),984(docker)
```

(1) **```2_create_postgresql_container.sh```** 実行
※下記のように出力されたらビルド完了です。

```bash
raspi@raspi-4-dev:~$ LANG=C
raspi@raspi-4-dev:~$ ./2_create_postgresql_container.sh 2>&1 \
 tee ~/work/logs/2_create_postgresql_container.log
#...途中割愛...
 Network postgres_default  Creating
 Network postgres_default  Created
 Container postgres-raspi  Creating
 Container postgres-raspi  Created
 Container postgres-raspi  Starting
 Container postgres-raspi  Started
Docker create PostgreSQL container >> status=0
 Container postgres-raspi  Stopping
 Container postgres-raspi  Stopped
 Container postgres-raspi  Removing
 Container postgres-raspi  Removed
 Network postgres_default  Removing
 Network postgres_default  Removed
Done, sudo reboot now.
```

(2) 手動でリブート

```bash
raspi@raspi-4-dev:~$ sudo reboot
```

(3) 開発用PCから再度ログインし、PostgreSQLコンテナの起動ステータスを確認する

```bash
raspi@raspi-4-dev:~$ systemctl status postgresql-docker.service 
○ postgresql-docker.service - PostgreSQL container start service
     Loaded: loaded (/etc/systemd/system/postgresql-docker.service; enabled; preset: enabled)
     Active: inactive (dead) since Fri 2024-11-15 10:11:52 JST; 1min 56s ago
   Duration: 1.104s
    Process: 1659 ExecStart=/bin/sh -c /home/raspi/bin/postgres-docker-service.sh start $BUILD_PATH (code=exited, status=0/SUCCESS)
   Main PID: 1659 (code=exited, status=0/SUCCESS)
        CPU: 865ms

11月 15 10:11:47 raspi-4-dev sh[1685]:  Network postgres_default  Created
11月 15 10:11:47 raspi-4-dev sh[1685]:  Container postgres-raspi  Creating
11月 15 10:11:48 raspi-4-dev sh[1685]:  Container postgres-raspi  Created
11月 15 10:11:48 raspi-4-dev sh[1685]:  Container postgres-raspi  Starting
11月 15 10:11:50 raspi-4-dev sh[1685]:  Container postgres-raspi  Started
11月 15 10:11:51 raspi-4-dev systemd[1]: Started postgresql-docker.service - PostgreSQL container start service.
11月 15 10:11:51 raspi-4-dev sh[1660]: PostgreSQL(/home/raspi/docker/postgres) container ready!
11月 15 10:11:52 raspi-4-dev sh[1660]: CONTAINER ID   IMAGE               COMMAND                  CREATED         STATUS         P>
11月 15 10:11:52 raspi-4-dev sh[1660]: e731e25b489a   postgres-postgres   "docker-entrypoint.s…"   5 seconds ago   Up 3 seconds   0>
11月 15 10:11:52 raspi-4-dev systemd[1]: postgresql-docker.service: Deactivated successfully.
```

(4) 実行中の PostgreSQLコンテナに入りテーブルが作成されたか確認する

```bash
raspi@raspi-4-dev:~$ docker exec -it postgres-raspi bash
e731e25b489a:/# psql -Udeveloper qiita_exampledb
psql (16.5)
Type "help" for help.

qiita_exampledb=# SET search_path TO public,mainte2;
SET
qiita_exampledb=# \dn
       List of schemas
  Name   |       Owner       
---------+-------------------
 mainte2 | developer
 public  | pg_database_owner
(2 rows)

qiita_exampledb=# \dt
              List of relations
 Schema  |      Name      | Type  |   Owner   
---------+----------------+-------+-----------
 mainte2 | ssh_auth_error | table | developer
 mainte2 | unauth_ip_addr | table | developer
(2 rows)

qiita_exampledb=# \dS unauth_ip_addr
                                Table "mainte2.unauth_ip_addr"
    Column    |         Type          | Collation | Nullable |             Default             
--------------+-----------------------+-----------+----------+---------------------------------
 id           | integer               |           | not null | nextval('ip_addr_id'::regclass)
 ip_addr      | character varying(15) |           | not null | 
 reg_date     | date                  |           | not null | 
 country_code | character(2)          |           |          | 
Indexes:
    "pk_unauth_ip_addr" PRIMARY KEY, btree (id)
    "idx_ip_addr" UNIQUE, btree (ip_addr)
Referenced by:
    TABLE "ssh_auth_error" CONSTRAINT "fk_ssh_auth_error" FOREIGN KEY (ip_id) REFERENCES unauth_ip_addr(id)

qiita_exampledb=# exit
e731e25b489a:/# exit
exit
```

### 最後に

今回の検証にラズパイ４を使った理由は、現在開発用PCでデータベースに登録している処理をラズパイ端末に移行することを検討しているからになります。

自宅サーバーは年中休まず稼働しているので不正アクセスしてくるホストのデータも毎日データベースに登録なければなりません。

ラズパイをこの用途のサーバーにすればデータベースへの登録作業が自動化できると考えています。不正アクセスしてきたホスト情報、そのホストが属する国情報などを可視化する計画があるので OSはデスクトップを選択しました。

この記事で紹介したソースは下記 GitHub で公開しています
「3-2 インストールアーカイブ作成」で示したリソースになります。
[pipito-yuko / qiita-posts / RaspberryPi / docker_on_ubuntu_desktop](https://github.com/pipito-yukio/qiita-posts/tree/main/RaspberryPi/docker_on_ubuntu_desktop)