有馬総一郎のブログ

(彼氏の事情)

2024年02月10日 19:26:57 JST - 7 minute read - Linux

それまでルートで起動してたDockerをルートレス化してみたら、色々変更が必要だった

ということで、 前回、デスクトップ環境での真っ新の状態からのdockerのルートレス化をやってみたのだが、今回は、サーバー環境で、従来の dokcer グループにユーザ追加して、既に起動しているdockerをルートレス化してみた。

そんな躓くこともないだろう?と高を括っていたが、盛大に躓いた。

ルートレス化できない

Ubuntu Server 22.04だったからか、前提条件は何もせずともクリアしていた。しかし、dockerd-rootless-setuptool.sh --force installに失敗する。

[ERROR] Aborting because rootful Docker (/var/run/docker.sock) is running and accessible. Set --force to ignore.
arimasou16@ubuntu:~$ dockerd-rootless-setuptool.sh --force install
[INFO] Creating /home/arimasou16/.config/systemd/user/docker.service
[INFO] starting systemd service docker.service
+ systemctl --user start docker.service
+ sleep 3
+ systemctl --user --no-pager --full status docker.service
● docker.service - Docker Application Container Engine (Rootless)
     Loaded: loaded (/home/arimasou16/.config/systemd/user/docker.service; disabled; vendor preset: enabled)
     Active: activating (auto-restart) (Result: exit-code) since Fri 2024-02-09 18:13:30 JST; 777ms ago
       Docs: https://docs.docker.com/go/rootless/
    Process: 3879 ExecStart=/usr/bin/dockerd-rootless.sh (code=exited, status=1/FAILURE)
   Main PID: 3879 (code=exited, status=1/FAILURE)
        CPU: 7ms

 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + mtu=
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + [ -z  ]
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + command -v slirp4netns
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + [ -z  ]
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + command -v vpnkit
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + echo Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + exit 1
 2月 09 18:13:30 ubuntu systemd[2411]: docker.service: Main process exited, code=exited, status=1/FAILURE
 2月 09 18:13:30 ubuntu systemd[2411]: docker.service: Failed with result 'exit-code'.
+ set +x
[ERROR] Failed to start docker.service. Run `journalctl -n 20 --no-pager --user --unit docker.service` to show the error log.
[ERROR] Before retrying installation, you might need to uninstall the current setup: `/usr/bin/dockerd-rootless-setuptool.sh uninstall -f ; /usr/bin/rootlesskit rm -rf /home/arimasou16/.local/share/docker`

既に答えが書いてあるが、

 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: + echo Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed
 2月 09 18:13:30 ubuntu dockerd-rootless.sh[3879]: Either slirp4netns (>= v0.4.0) or vpnkit needs to be installed

ということでsudo apt install slirp4netnsをする。そしたら、次は成功。前提条件にslirp4netnsはなかったぞ…vpnkitだったりするんだろうが。

Dockerのコンテナーからhostに繋がらない

ルートレス化したので、そのままの設定で、Joplin Serverイメージを引っ張り直して起動してみる。しかし、ブラウザで見ると繋らない。

Joplin Serverのログをみると2024-02-09 09:48:50: [error] db: Timeout trying to connect to database: Error: connect ECONNREFUSED 127.0.0.1:5432とホスト側で起動しているpostgresに繋がらない。

既知の制限事項に書いてあったのだが、

ホストネットワーク ( docker run –net=host) も RootlessKit 内で名前空間化されます。

ということで繋げられない?

Dockerコンテナ内からホストのlocalhostにアクセスするという、まさにピンポイントな記事があった。

  1. Dockerコンテナと同じネットワーク内でサーバーを立てる
  2. UNIXソケットを使ってTCP通信を中継する
  3. Rootless Dockerでの–disable-host-loopbackの指定を解除する
  4. ngrokなどを使ってグローバルに公開する

とあったので、まず一番邪道そうな 3--disable-host-loopbackを外すをやったみたのだけど、やりかたが悪いのか?

  1. /usr/bin/dockerd-rootless.sh--disable-host-loopbackを削除
  2. docker.service再起動やらサーバ再起動

駄目だった…1

その次、 2linux - How to access localhost on rootless docker - Stack Overflowのアンサーでのシェルを試してみた。

#!/bin/bash
set -e

PORTS=($(echo "$1" | grep -oP '^\d+(:\d+)?$' | sed -e 's/:/ /g'))
if [ -z $PORTS ]; then
    cat <<EOF
Usage:
$(basename "$0") SRC[:DEST]
    SRC: will be the port accessible inside the container
    DEST:
        the connection will be redirected to this port on the host.
        if not specified, the same port as SRC will be used
EOF
    exit 1
fi

SOURCE=${PORTS[0]}
DEST=${PORTS[1]-${PORTS[0]}}

SOCKFILE="$XDG_RUNTIME_DIR/forward-docker2host-${SOURCE}_$DEST.sock"
socat UNIX-LISTEN:"$SOCKFILE",fork TCP:127.0.0.1:$DEST &
nsenter -U --preserve-credentials -n -t $(cat "$XDG_RUNTIME_DIR/docker.pid") -- socat TCP4-LISTEN:$SOURCE,reuseaddr,fork "$SOCKFILE" &
echo forwarding $SOURCE:$DEST... use ctrl+c to quit
sleep 365d

sudo apt install socatをしてから、早速実行。Joplin Serverでのログを見ると、あっさり繋がるようになったのだけど…nginxや他でも修正が必要なのか、ブラウザからJoplin Serverに繋がらない。そもそも理屈を良く分かってないので止めた。

ホストのPostgreSQLデータをDockerに移行

正直、もう今までのやり方でいいや、とか心挫かけたが今の流行に合わせておくということで、 1 の正攻法、postgresもDockerで起動させてdocker composeで接続させることにした。

postgres - Official Image | Docker Hubを眺めつつ思案した結果、どうせなら軽量の Alpine LinuxのPostgreSQLの最新版を使うことにした。

などを参考に、移行。

$ sudo -u postgres pg_dumpall -U postgres > postgres_dumpall.out
$ docker run --name postgresql_server  -e POSTGRES_PASSWORD=パスワード -d postgres:16-alpine
$ docker cp postgres_dumpall.out コンテナID:/tmp/postgres_dumpall.out
$ docker exec -i -t コンテナID /bin/bash
# su postgres -c "psql -f /tmp/postgres_dumpall.out"

上記で移行できるが、 ボリュームを使ったコンテナの起動をしてないので、結局-v ./postgresql/db:/var/lib/postgresql/dataのようにボリュームを使ってからやり直すこととなった。

postgresみたいなデータベースといった永続的なデータを扱うのにdockerってどうなの?って思ってたら、 /var/lib/postgresql/data をホストと共有することでコンテナ削除しても問題なく使えるんだねぇ。

適当に compose.yaml を作成してdocker compose up -dすると、panic: interface conversion: interface {} is map[string]interface {}, not stringとのエラーが…

      environment:
        - APP_BASE_URL: https://example.com/joplin

環境変数の設定部分を=とするのを: としてしまったことが原因だった。

コンテナ同士で接続できない、コンテナ外部から接続できない

postgresコンテナの後に、joplin serverを起動させるが、Joplin Serverからpostgresコンテナに繋がらない。

他のDockerコンテナからコンテナ内のMySQLに接続する #Docker - Qiitaを参考に同じネットワーク空間で起動するようにして、POSTGRES_HOST=postgresql_serverと環境変数を設定すれば接続できるようになった。2

よっしゃ!と思ったら、今度はコンテナ外部からコンテナに繋がっていない… Dockerのコンテナ外部から内部への接続 | FindxFineのとおり、postgresコンテナに-p 5432:5432を付け忘れていた。というか、同じポート番号だから要らんだろ、みたいな勘違いしていた。

とすったもんだあった挙句、下のような compose.yaml が完成した。

version: '3'

services:
  db:
      image: postgres:16-alpine
      container_name: postgresql_server
      restart: unless-stopped
      volumes:
        - ./postgresql/db:/var/lib/postgresql/data
        - ./postgresql/log:/var/log
      environment:
        - POSTGRES_PASSWORD=postgresのパスワード
        - PGDATA=/var/lib/postgresql/data/pgdata
      networks:
        - app-net
      ports:
        - "5432:5432"
  app:
      image: joplin/server:latest
      container_name: joplin_server
      depends_on:
        - db
      ports:
        - "22300:22300"
      restart: unless-stopped
      environment:
        - APP_BASE_URL=https://example.com/joplin
        - APP_PORT=22300
        - DB_CLIENT=pg
        - POSTGRES_PASSWORD=joplinデータベースのパスワード
        - POSTGRES_USER=joplinデータベースのユーザ名
        - POSTGRES_DB=joplinデータベース名
        - POSTGRES_PORT=5432
        - POSTGRES_HOST=postgresql_server
      networks:
        - app-net
networks:
  app-net:
    driver: bridge

新たに覚えたdockerコマンド

  • コンテナのログを見る

    docker logs -f コンテナ名

  • composeで実行中のコンテナに入る

    docker compose exec サービス名 /bin/bash コンテナ名では入れなくてサービス名servicesなんですね。

  • ネットワーク情報の詳細を調べる

    docker network inspect ネットワーク名

ホスト側やらルートDockerの後始末

最後、それまでルートで作成したdockerのイメージ、コンテナは見えなくなっていたので、dockerコマンドでなく、普通にrm3で削除した。良い子は真似しちゃ駄目。それと、ホスト側のpostgresも削除した。

sudo gpasswd -d $USER docker
sudo rm -rf /var/lib/docker/overlay2/*
sudo rm -rf /var/lib/docker/containers/*
sudo systemctl stop postgresql.service 
sudo systemctl disable postgresql.service 
sudo apt autopurge postgresql*
sudo apt autopurge postgresql-*
sudo rm -rf /etc/postgresql-common /var/lib/postgresql /etc/postgresql

これ以上、問題が起きないことを願う。


  1. /usr/bin/dockerd-rootless-setuptool.sh uninstall -f ; /usr/bin/rootlesskit rm -rf /home/arimasou16/.local/share/dockerしてないから? ↩︎

  2. Joplin Sync Server is Awesome - Install Joplin Server Using Docker Composeなど見るとnetworksの設定なしに db とやれば良いみたいに書いてあるから、単純にPOSTGRES_HOST=の指定漏れ・ミスだけの可能性もある。 ↩︎

  3. 何か設定弄ったのか、ディレクトリ配下を*での一括削除が出来なかったので、本当は一つ一つ指定して削除した。 ↩︎