Podmanでtraefik + owncloud/ocis (仮)

完成形ではないのだけれど、設定の細かい部分を忘れてしまいそうなので、作業途中の備忘録として残す。タイトルが仮なのは、まだ色々追加もするしテスト運用したら更新するはずなので、その場合はより新しいブログエントリーを参照して欲しい、という意。

やりたいこと

RHEL9のPodmanで、Traefik Proxy + ownCloud Infinite Scale (ocis)を動かしたい。つまりSSL/TLS化やLetsencryptの管理をTraefikに任せて、楽をしたい。WordpressやPlex、Pi-holeも対象だが、まずはocisから。

Docker Composeはある

この構成用のdocker-composeのyamlは既に存在しているので、これをPodmanのコマンドに変換して動いた後にpodman generateすれば良いだろうと。

事前準備

ここで用いるホスト名の名前解決が出来るようにネームサーバを設定しておくこと。Letsencryptが80/tcpにアクセスしてくるため(これはtraefikやocisの要件ではない)。

このエントリーではRHEL9.3を前提にしている。ひとまず動かすことが目的なのでSELinuxはDisabledにしてあるが本番はEnforcingとする。パッケージグループは以下。

  • @^server-product-environment
  • @container-management

インストール完了後、container-toolsを追加インストールする。

# dnf -y install container-tools

さらに、Dockerとの互換性のためにdocker.sockを使えるようにする。

# systemctl enable --now podman.socket
# ll /var/run/docker.sock
lrwxrwxrwx 1 root root 23 Jan 31 17:20 /var/run/docker.sock -> /run/podman/podman.sock

なお、以下の手順は80/tcpと443/tcpを使うために余計な手順を踏みたくないためrootfulで動かしている。rootlesにしたい場合は、各自で調べて欲しい。

ocisの初期化

特に難しいことはない。

# podman run \
    --rm \
    -it \
    --name ocis-runtime \
    --volume ocis-config:/etc/ocis \
    --volume ocis-data:/var/lib/ocis \
    docker.io/owncloud/ocis init
Do you want to configure Infinite Scale with certificate checking disabled?
 This is not recommended for public instances! [yes | no = default] yes

=========================================
 generated OCIS Config
=========================================
 configpath : /etc/ocis/ocis.yaml
 user       : admin
 password   : PQyY=VS25!lQJAI8H#C239rVqU=K-xNb

“Do you want to configure…?”は、本番では”no”とすること。ここで”yes”を選択すると、OCIS_INSECURE=trueをenvで指定したのと同じで、ocis.yaml中にある15箇所の[insecure]が”true”になる。

[volume]は2つ、ocis-config / ocis-dataで、/var/lib/containers/storage/volumes/以下に置かれる。ocis-dataにはocisにアップロードしたファイルが置かれるので容量に注意。bindマウントとして、別の場所に置いても良い。

ocis initで生成されたパスワードはocis.yamlに保存されている。

Podを作る

これも特に難しくはない。80/tcpと443/tcpをpublishするだけ。

# podman pod create -p 80:80 -p 443:443 --name app-pod

podの停止、削除等の操作は以下。

# podman pod ps # pod用のps
# podman pod logs (-c container) app-pod # "app-pod" podのログ、-c containerでpod中のコンテナを指定
# podman pod pause app-pod # 中断
# podman pod unpause app-pod # 再開
# podman pod stop app-pod # 停止
# podman pod start app-pod # 起動
# podman pod rm (-f) app-pod # 削除、-fで停止・削除
# podman pod rm -a app-pod # pod内のコンテナの全削除
# podman pod prune -f # podの関連リソースの削除
# podman volume ls # ボリュームの表示
# podman volume rm (-a) # ボリュームの削除、-aで全削除

TraefikのコンテナをPodに追加

podman run \
    --detach \
    --pod app-pod \
    --name traefik-runtime \
    --restart always \
    -l "traefik.enable=true" \
    -l "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$2y$$05$$BQbbR9ERTpqsd1BVwlEvz.KKsvb.EDwvBOP0Evki2UISg1Jg9dX0." \
    -l "traefik.http.routers.traefik-runtime.entrypoints=https_ep" \
    -l "traefik.http.routers.traefik-runtime.rule=Host(\`traefik.example.com\`)" \
    -l "traefik.http.routers.traefik-runtime.middlewares=traefik-auth" \
    -l "traefik.http.routers.traefik-runtime.tls.certresolver=myresolver" \
    -l "traefik.http.routers.traefik-runtime.service=api@internal" \
    -l "traefik.http.services.traefik-runtime.loadbalancer.server.port=8080" \
    --volume /var/run/docker.sock:/var/run/docker.sock:ro \
    --volume certs:/certs \
    docker.io/library/traefik \
        --log.level=ERROR \
        --certificatesresolvers.myresolver.acme.email=cert@example.com \
        --certificatesresolvers.myresolver.acme.storage=/certs/acme.json \
        --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=http_ep \
        --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory \
        --api.dashboard=true \
        --entrypoints.http_ep.address=:80 \
        --entrypoints.http_ep.http.redirections.entrypoint.to=https_ep \
        --entrypoints.http_ep.http.redirections.entrypoint.scheme=https \
        --entrypoints.https_ep.address=:443 \
        --providers.docker.endpoint=unix:///var/run/docker.sock \
        --accesslog=true \
        --accesslog.format=json \
        --accesslog.fields.headers.names.X-Request-Id=keep

ここから一気に難易度が上がり、とても全ての項目を覚えておけないので、このエントリーを書こうと思った理由となっている。難しい部分は”-l”、つまり”–label”と、コマンドの引数。また、参考にしたdocker-compose.yamlはリゾルバやエントリーポイントの命名があまりよろしくなく設定時にかなり混乱したため、ここでは修正している。

重要なポイントは、”–pod”で実行したコンテナを入れるPodを指定する、というところ。

では設定を見ていく。まずラベルから。

-l "traefik.enable=true" \
-l "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$2y$$05$$BQbbR9ERTpqsd1BVwlEvz.KKsvb.EDwvBOP0Evki2UISg1Jg9dX0." \
-l "traefik.http.routers.traefik-runtime.entrypoints=https_ep" \
-l "traefik.http.routers.traefik-runtime.rule=Host(\`traefik.example.com\`)" \
-l "traefik.http.routers.traefik-runtime.middlewares=traefik-auth" \
-l "traefik.http.routers.traefik-runtime.tls.certresolver=myresolver" \
-l "traefik.http.routers.traefik-runtime.service=api@internal" \
-l "traefik.http.services.traefik-runtime.loadbalancer.server.port=8080" \

ここは、traefikのWUIを有効にするための設定で、traefikのルーターを使ってtraefikのWUIを公開するようにしている。以下、逐条解説。

  • traefik.enable=true: このコンテナ”traefic-runtime”をtraefikによって公開する
  • traefik.http.middlewares.traefik-auth.basicauth.users: WUIのBasic認証の設定。traefik-authは組み込みのミドルウェア。
    echo $(htpasswd -nB admin) | sed -e s/\\$/\\$\\$/g
    で生成できるとなっているが、実際にこの生成値では認証を通らない(要確認
  • traefik.http.routers.traefik-runtime.entrypoints=https_ep: “traefik-runtime”と名付けたコンテナのエントリーポイントは”https_ep”。”https_ep”の定義は、コマンド引数にある。
  • traefik.http.routers.traefik-runtime.rule=Host(`traefik.example.com`): “traefik.example.com”でhttpリクエストされたら、”traefik-runtime”にルーティングするルールの定義。このホスト名は、“`”(バックティック)で囲むこと。
  • traefik.http.routers.traefik-runtime.middlewares=traefik-auth: 認証のミドルウェアの設定。
  • traefik.http.routers.traefik-runtime.tls.certresolver=myresolver: TLSの認証リゾルバの設定。”myresolver”の定義は、コマンド引数にある。
  • traefik.http.routers.traefik-runtime.service=api@internal: サービスの設定。traefik APIを指定している。
  • traefik.http.services.traefik-runtime.loadbalancer.server.port=8080: traefikのWUIは8080/tcpとなっているので、ここまでの設定で「https_ep(:443)に”traefik.example.com”へのリクエストが来たら、8080/tcpにルーティングし、実際の処理はapi@internalが行う」となる。

次にコマンド引数を見る。

--log.level=ERROR \
--certificatesresolvers.myresolver.acme.email=cert@example.com \
--certificatesresolvers.myresolver.acme.storage=/certs/acme.json \
--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=http_ep \
--certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory \
--api.dashboard=true \
--entrypoints.http_ep.address=:80 \
--entrypoints.http_ep.http.redirections.entrypoint.to=https_ep \
--entrypoints.http_ep.http.redirections.entrypoint.scheme=https \
--entrypoints.https_ep.address=:443 \
--providers.docker.endpoint=unix:///var/run/docker.sock \
--accesslog=true \
--accesslog.format=json \
--accesslog.fields.headers.names.X-Request-Id=keep
  • –log.level=ERROR: 特に説明は不要。
  • –certificatesresolvers.myresolver.acme.email=cert@example.com: ここからlabelで設定されていた”myresolver”の定義。ACMEプロトコルで証明書を発行する際のメールアドレス。
  • –certificatesresolvers.myresolver.acme.storage=/certs/acme.json: 発行された証明書のメタ情報を保存するJSONの保存先。
  • –certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=http_ep: ACMEプロトコルのHTTPチャレンジのエントリーポイント。”http_ep”は後続の引数で指定している通り、80/tcp。
  • –certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory: Letsencryptのステージング用サーバの指定。テスト用なので本番では不要。ただし本番のLetsencryptには発行回数制限があり(たぶん)168時間に5回まで。それを超えるとアクセス拒否される。
  • –api.dashboard=true: ダッシュボード(WUI)を有効にする。
  • –entrypoints.http_ep.address=:80: エントリーポイント”http_ep”の設定。80/tcp。
  • –entrypoints.http_ep.http.redirections.entrypoint.to=https_ep: 80/tcpにhttpプロトコルでアクセスしてきたら、エントリーポイント”https_ep”にフォワードする設定。
  • –entrypoints.http_ep.http.redirections.entrypoint.scheme=https: フォワードする際に、httpsプロトコルに変更する設定。
  • –entrypoints.https_ep.address=:443: エントリーポイント”https_ep”の設定。443/tcp。
  • –providers.docker.endpoint=unix:///var/run/docker.sock: traefikでは多くの種類のプロバイダーが使えるが、Docker APIもその一つ。Podman環境では、Podmanが存在しないDockerデーモンをエミュレートしており、そのインターフェイスはpodman.sockというUNIXソケット。podman.socketをsystemctlで有効にしてあるので、/var/run/docker.sockにマウントされている。
  • 以下、アクセスログの設定。

ocisのコンテナをPodに追加

podman run \
    --detach \
    --pod app-pod \
    -it \
    --name ocis-runtime \
    --restart always \
    -e OCIS_URL=https://owncloud.example.com \
    -e PROXY_TLS=false \
    --volume ocis-config:/etc/ocis \
    --volume ocis-data:/var/lib/ocis \
    -l "traefik.enable=true" \
    -l "traefik.http.routers.ocis.entrypoints=https_ep" \
    -l "traefik.http.routers.ocis.rule=Host(\`owncloud.example.com\`)" \
    -l "traefik.http.routers.ocis.tls.certresolver=myresolver" \
    -l "traefik.http.routers.ocis.service=ocis" \
    -l "traefik.http.services.ocis.loadbalancer.server.port=9200" \
    docker.io/owncloud/ocis

traefikと同様に逐条解説。まず環境変数。

  • OCIS_URL=https://owncloud.example.com: ocisのURL
  • PROXY_TLS=false: ocisのプロキシ機能でTLS化しない設定。ここがtrueだと、traefikとの通信が出来なくなるので注意。

次にラベル。

  • traefik.enable=true: このocisコンテナ”ocis-runtime”を公開する設定。
  • traefik.http.routers.ocis.entrypoints=https_ep: ocisのエントリーポイント。”https_ep”
  • traefik.http.routers.ocis.rule=Host(`owncloud.example.com`): ルール。
  • traefik.http.routers.ocis.tls.certresolver=myresolver: 証明書のリゾルバ。
  • traefik.http.routers.ocis.service=ocis: ここが少々不明。docker-composeであれば、サービス名は明示的に指定されるが、podmanで–nameで与えた名前がどのように解決されているかが良く分かっていない。”ocis-runtime”では動かず、”ocis”では動く。(要確認)
  • traefik.http.services.ocis.loadbalancer.server.port=9200: ocisのポートは9200/tcp

その他

docker-compose.yamlによれば、ocisコンテナのコマンドとして、/bin/sh -c ocis init || true; ocis serverで動くように書かれているので、IDM_ADMIN_PASSWORDとOCIS_INSECUREを環境変数に追加しコマンドを渡すようにしてみたが、”400 Bad gateway”となり動かない。このため、Podを作成する前に一度ocisを初期化する手順としている。

podman run \
    --detach \
    --pod app-pod \
    -it \
    --name ocis-runtime \
    --restart always \
    -e OCIS_URL=https://owncloud.example.com \
    -e PROXY_TLS=false \
    -e IDM_ADMIN_PASSWORD=password \
    -e OCIS_INSECURE=true \
    --volume ocis-config:/etc/ocis \
    --volume ocis-data:/var/lib/ocis \
    -l "traefik.enable=true" \
    -l "traefik.http.routers.ocis.entrypoints=https_ep" \
    -l "traefik.http.routers.ocis.rule=Host(\`owncloud.example.com\`)" \
    -l "traefik.http.routers.ocis.tls.certresolver=myresolver" \
    -l "traefik.http.routers.ocis.service=ocis" \
    -l "traefik.http.services.ocis.loadbalancer.server.port=9200" \
    docker.io/owncloud/ocis \
        init \|\| true\; ocis server

(ひとまず)まとめ

数カ所怪しいところがあるが、これで一応動くことは確認できた。