【Podman v3】ルートレスモードでdocker-composeを実行する

Red Hatでソリューションアーキテクトをしている田中司恩(@tnk4on)です。

Red Hat Enterprise Linux(以下、RHEL) 8向けPodmanの新しいパッケージ(v3.2.3)が公開され、ルートレスモードでdocker-composeが使用できるようになりました以前の記事ではrootモードでdocker-composeを動かす方法を紹介しました。

rheb.hatenablog.com

今回はPodman v3.2以降で対応したルートレスモードでdocker-composeを実行する方法について紹介します。


Podman v3.2.3のアップデート

2021年8月10日にRHEL 8 向けのcontainer-toolsのアップデートが公開されています。

Podman v3.2.3のパッケージ情報はこちら。

アップデート後のPodmanのバージョンは下記のようになっています。

$ podman version
Version:      3.2.3
API Version:  3.2.3
Go Version:   go1.15.7
Built:        Tue Jul 27 16:29:39 2021
OS/Arch:      linux/amd64

Podman v3.2.3へアップデートするにはdnfコマンドでcontainer-toolsまたはpodman単体のアップデートを実施して下さい。

$ sudo dnf update podman

または

$ sudo dnf module update container-tools

ルートレスでpodman.socketサービスを起動する

ルートレスでdocker-composeを実行する場合もrootモードと同じくPodman APIソケット(podman.socketサービス)の起動が必要です。起動するには一般ユーザーで下記コマンドを実行します。

パッケージのアップデート後、念の為systemd マネージャー設定を再読み込み

$ systemctl --user daemon-reload

podman.socketサービスを有効にし、すぐにサービスを起動

$ systemctl --user enable --now podman.socket
Created symlink /home/user/.config/systemd/user/sockets.target.wants/podman.socket → /usr/lib/systemd/user/podman.socket.

サービスが起動しているか確認

$ systemctl --user status podman.socket
● podman.socket - Podman API Socket
   Loaded: loaded (/usr/lib/systemd/user/podman.socket; enabled; vendor preset: enabled)
   Active: active (listening) since Mon 2021-08-16 01:16:36 JST; 26s ago
     Docs: man:podman-system-service(1)
   Listen: /run/user/1000/podman/podman.sock (Stream)
   CGroup: /user.slice/user-1000.slice/user@1000.service/podman.socket

 8月 16 01:16:36 localhost.localdomain systemd[12425]: Listening on Podman API Socket.

Podman APIの動作確認

$ curl -s --unix-socket /run/user/1000/podman/podman.sock http://d/v3.0.0/libpod/info | jq .version

出力結果を開く

{
  "APIVersion": "3.2.3",
  "Version": "3.2.3",
  "GoVersion": "go1.15.7",
  "GitCommit": "",
  "BuiltTime": "Tue Jul 27 16:29:39 2021",
  "Built": 1627370979,
  "OsArch": "linux/amd64"
}

  • ルートレスモードのPodman APIソケットのパスはsystemctl --user status podman.socketの出力内容を参照
  • Podman APIソケットの詳細については以前の記事を参考にしてください

次はdocker-composeコマンドの準備を行います。

(参考)RHEL 8製品ドキュメントの該当項目について

RHEL 8の製品ドキュメントにも該当項目がありますが内容が古いのであまり参考になりません。現時点では参考程度にご参照下さい。

docker-composeコマンドをコンテナで実行する

以前の記事ではdocker-composeのインストールにpipを使用しましたが、Python/pipの環境に余計なものを入れたくない場合もあると思います。また、Docker公式ドキュメントではGitHubリポジトリからdocker-composeバイナリを入手して使用する方法が紹介されています。*1

これらの方法に代わり、docker-compose自体をコンテナで実行することで不要なものを追加せずにdocker-composeコマンドを実行することが可能となります。コンテナイメージはDocker公式が公開しているものがあるのでそちらを利用します。

コンテナでdocker-composeを実行する時の構成は下記のようになります。

コンテナ実行のdocker-composeの構成
コンテナ実行のdocker-composeの構成

下記の内容を~/.bashrcに追記することで、コンテナ実行のコマンドをdocker-composeのエイリアスとして使用できます。

alias docker-compose='podman run --rm \
    -v /run/user/1000/podman/podman.sock:/var/run/docker.sock:z \
    -v "$PWD:/$PWD:z" \
    -w "/$PWD" \
    docker/compose:1.29.2'
  • オプションの補足
    • -v /run/user/1000/podman/podman.sock:/var/run/docker.sock:z :ルートレスモードのAPIソケットをコンテナ内のdocker.sockにマウントする
    • -v "$PWD:/$PWD:z" :カレントディレクトリをコンテナ内のWORKDIRにマウントする
    • -w "/$PWD" :コンテナ内のWORKDIRを指定
    • docker/compose:1.29.2 :実行するdocker-composeのイメージタグを指定。最新は1.29.2。

編集後、ファイルの再読み込み

$ source ~/.bashrc

初回実行時のみコンテナイメージを先に取得しておく(2回目以降の実行は不要)

$ podman pull docker.io/docker/compose:1.29.2

docker-composeの動作確認

1) 作業ディレクトリを作成し、SELinuxコンテキストを変更する

$ mkdir test && cd test
$ chcon -R -t container_file_t .
$ ls -lZ ../
合計 0
drwxrwxr-x. 2 user user unconfined_u:object_r:container_file_t:s0 6  8月 16 21:02 test
  • docker-composeのエイリアス実行時にカレントディレクトリをマウントするため、container_file_tのラベルが設定されていることが必要です

2) docker-compose versionを実行し、正常にコマンドが実行できることを確認

$ docker-compose version
docker-compose version 1.29.2, build 5becea4
docker-py version: 5.0.0
CPython version: 3.7.10
OpenSSL version: OpenSSL 1.1.1k  25 Mar 2021

これでルートレスでdocker-composeを実行する環境が整いました。

ルートレスでdocker-composeを実行してみる

以前の記事と同じくdocker-composeでWordPressを実行します。 docker-compose.ymlも同様にDocker Hubに記載のサンプルを使用します。

作業ディレクトリを作成し、docker-compose.ymlファイルを作成

$ mkdir wordpress && cd wordpress
$ chcon -R -t container_file_t .
$ vi docker-compose.yml

docker-compose.ymlの内容

version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:

docker-compose upの実行(※SELinuxのエラーが出た場合は次項の対応を実施)

$ docker-compose up -d
Creating network "wordpress_default" with the default driver
Creating volume "wordpress_wordpress" with default driver
Creating volume "wordpress_db" with default driver
Pulling wordpress (wordpress:)...
Pulling db (mysql:5.7)...
Creating wordpress_wordpress_1 ...
Creating wordpress_db_1        ...
Creating wordpress_db_1        ... done
Creating wordpress_wordpress_1 ... done
  • エイリアスコマンドにPodman APIソケット(及びdocker.sock)を直接指定しているので事前にDOCKER_HOST環境変数の設定は不要です

起動したコンテナの確認

$ podman ps -a
CONTAINER ID  IMAGE                               COMMAND               CREATED        STATUS            PORTS                 NAMES
6098cc19a0d7  docker.io/library/mysql:5.7         mysqld                8 seconds ago  Up 7 seconds ago                        wordpress_db_1
6f6d0aa34eb0  docker.io/library/wordpress:latest  apache2-foregroun...  8 seconds ago  Up 7 seconds ago  0.0.0.0:8080->80/tcp  wordpress_wordpress_1

ブラウザでhttp://<ホストのIPアドレス>:8080へアクセスしてWordPressのインストール画面が表示されたらOKです。

SELinuxエラーの対応

docker-compose up時に下記のようなエラーが出た場合はSELinuxのポリシーに引っかかっています

$ docker-compose up -d
[8] Failed to execute script docker-compose
Traceback (most recent call last):
  File "urllib3/connectionpool.py", line 677, in urlopen
  File "urllib3/connectionpool.py", line 392, in _make_request
  File "http/client.py", line 1277, in request
  File "http/client.py", line 1323, in _send_request
  File "http/client.py", line 1272, in endheaders
  File "http/client.py", line 1032, in _send_output
  File "http/client.py", line 972, in send
  File "docker/transport/unixconn.py", line 43, in connect
PermissionError: [Errno 13] Permission denied

(以下省略)

audit2allowコマンドを使ってモジュールの作成とインストールを行います

$ sudo ausearch -c 'docker-compose' --raw | audit2allow -M my-dockercompose
$ sudo semodule -i my-dockercompose.pp

my-dockercompose.te の内容

$ cat my-dockercompose.te

module my-dockercompose 1.0;

require {
    type container_t;
    type container_runtime_t;
    class unix_stream_socket connectto;
}

#============= container_t ==============
allow container_t container_runtime_t:unix_stream_socket connectto;

再度docker-compose upを実行してエラーが出ないことを確認します。

ルートレスモードとpodman network

ルートレスモードでdocker-composeが使えるようになった背景には、ルートレスモードでのネットワーク構成機能の追加があります(下記のRFEを参照)。

github.com

ルートレスモードでdocker-compose実行時のネットワーク構成の変化を確認してみます。

docker-compose起動前

$ podman network ls
NETWORK ID  NAME        VERSION     PLUGINS

docker-compose起動後

$ podman network ls
NETWORK ID    NAME               VERSION     PLUGINS
b089aa3ab1f2  wordpress_default  0.4.0       bridge,portmap,firewall,tuning
$ podman network inspect wordpress_default

出力結果を開く

[
    {
        "args": {
            "podman_labels": {
                "com.docker.compose.network": "default",
                "com.docker.compose.project": "wordpress",
                "com.docker.compose.version": "1.29.2"
            }
        },
        "cniVersion": "0.4.0",
        "name": "wordpress_default",
        "plugins": [
            {
                "bridge": "cni-podman1",
                "hairpinMode": true,
                "ipMasq": true,
                "ipam": {
                    "ranges": [
                        [
                            {
                                "gateway": "10.89.0.1",
                                "subnet": "10.89.0.0/24"
                            }
                        ]
                    ],
                    "routes": [
                        {
                            "dst": "0.0.0.0/0"
                        }
                    ],
                    "type": "host-local"
                },
                "isGateway": true,
                "type": "bridge"
            },
            {
                "capabilities": {
                    "portMappings": true
                },
                "type": "portmap"
            },
            {
                "backend": "",
                "type": "firewall"
            },
            {
                "type": "tuning"
            }
        ]
    }
]

このようにdocker-compose upと同時に新しくネットワーク構成が作成されたことが確認できます。なお、docker-compose downを実行するとこのネットワーク構成は削除されます。

ルートレスモードにおける所有権の問題

ルートレスモードでコンテナ実行時の課題として、コンテナ内にホストのディレクトリをマウントして使用する場合の所有権の問題があります。 なお、PodmanのルートレスモードにおけるUIDの取り扱いについてはこの記事では詳細には触れませんが、過去の翻訳記事が参考になります。

rheb.hatenablog.com

例えばWordPressを使用する場合、wordpressおよびmysqlコンテナにマウントするディレクトリをホスト上に作成し、再度docker-compose upしてみると作成したフォルダのパーミッションがユーザー自身のものから変更されていることが確認できます。

再現手順

先程のwordpressフォルダの中にwordpressdbの2つのフォルダを作成する

$ mkdir wordpress db
$ chmod 777 wordpress db
$ vi docker-compose.yml

docker-compose.ymlの内容

version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - ./wordpress:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - ./db:/var/lib/mysql

#volumes:
#  wordpress:
#  db:

docker-compose up後のカレントディレクトリの所有権を確認

$ ls -l
合計 12
drwxrwxrwx. 6 100998 user   4096  8月 17 01:15 db
-rw-rw-r--. 1 user   user    585  8月 17 01:12 docker-compose.yml
drwxr-xr-x. 5 100032 100032 4096  8月 17 01:15 wordpress

この場合、UIDの100998100032はコンテナ内で実行しているプロセスのホスト上のUIDです。下記ではwww-datamysqlが該当します。

$ podman top wordpress_wordpress_1 user huser
USER        HUSER
root        1000
www-data    100032
www-data    100032
www-data    100032
www-data    100032
www-data    100032
$ podman top wordpress_db_1 user huser
USER        HUSER
mysql       100998

ホスト上では一般ユーザーとしてuserUID:1000で実行されていますので異なる所有権のファイル/フォルダは直接操作が不可となります。

$ rm -rf db wordpress
rm: 'db/mysql' を削除できません: 許可がありません
rm: 'db/performance_schema' を削除できません: 許可がありません
rm: 'db/sys' を削除できません: 許可がありません
rm: 'db/exampledb' を削除できません: 許可がありません
rm: 'wordpress/.htaccess' を削除できません: 許可がありません
(以下、省略)

ホスト側とファイルのやり取りを行うにしても都度sudoコマンドを使用すればできなくも無いですが、所有権を意識しながらの操作となるのでかなり手間です。 対処としてはPodmanにはpodman unshareという便利なコマンドが用意されています。

$ podman unshare
# ls -l
合計 12
drwxrwxrwx. 6 systemd-coredump root 4096  8月 17 01:15 db
-rw-rw-r--. 1 root             root 585  8月 17 01:12 docker-compose.yml
drwxr-xr-x. 5               33 tape4096  8月 17 01:15 wordpress
  • podman unshareコマンドを使用する実行する一般ユーザーのUser Namespaceに入った状態になる
  • 先程のUID100998100032から所有者の表示が変更されていることが確認できる

このようにpodman unshareを使うことでsudoを使わずにUser Namespace内で直接ファイル、ディレクトリの操作が行うことができるようになります。

podman unshareについては下記のブログも参考にしてください。

まとめ

Podman v3.2以降で対応したルートレスでdocker-composeを実行する方法を紹介しました。 RHEL 8向けのPodmanパッケージもv3.2.3へと更新されたのでRHEL 8でもすぐに使うことが可能です。 docker-composeの実行についてはコンテナとして実行する方法を紹介しました。これにより実行元ホストに追加でインストール作業が不用になります。 また、ルートレスモードの課題としてディレクトリマウント時に所有権の問題を紹介しました。podman unshareを使うことで一般ユーザーのままホスト側とコンテナ間でのファイル操作などが容易になります。

なお、docker-composeを使用せずにpodman runで直接コンテナを起動する場合は--userns keep-idという便利なオプションが使えます。しかしながらこれはPodman固有のオプションのためDockerのためのアプリであるdocker-composeでは実装されておらず使用できません。複数のコンテナを実行しつつホスト側のディレクトリをマウントする場合はPodman podsの利用を推奨します。 docker-compose から Podman podsへ移行する方法についてはまた別の機会に紹介したいと思います。

*1:pipでのインストールは代替え手段として書かれているので、本来はこちらがメインのインストール方法の模様

* 各記事は著者の見解によるものでありその所属組織を代表する公式なものではありません。その内容については非公式見解を含みます。