🦭

MitraをRootless Podmanで動かす

に公開

MitraをRootless Podmanで動かす。またPodman Quadletを使いサービス化する。

環境

  • OS
    • AlmaLinux10
  • Podman
    • 5.4.0

構成

  • CaddyServerをリバースプロキシとして使う
  • 各コンテナはpodに配置する
  • 通信は以下の通り
    • internet --80/443--> nftables
    • nftables --8080/8443--> caddy
    • caddy --8383--> mitra
      • Pod間通信: apnet
    • mitra --5432--> postgres
      • Pod間通信: apnet

AlmaLinuxでRootless Podmanを動かす

sudo usermod --add-subuids 100000-165536 --add-subgids 100000-165536 $USER
podman system migrate
  • nftablesでポートフォワーディングを行う
    • Rootless Podmanでは1024番未満のポートは使えないため、 80/443ポートを8080/8443ポートにポートフォワーディングする
table inet nat {
        chain prerouting {
                type nat hook prerouting priority dstnat; policy accept;
                tcp dport 80 redirect to :8080
                tcp dport 443 redirect to :8443
        }
}
  • CaddyServerのquic-go関連でエラーがでるので/etc/sysctl.d/99-sysctl.confに以下を追加
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000
[Slice]
Slice=user.slice

CPUAccounting=yes
MemoryAccounting=yes
IOAccounting=yes
TasksAccounting=yes

各コンテナの準備

フォルダ構成

.
└── mitra/
    ├── conf/
    │   └── Caddyfile
    ├── kube/
    │   ├── caddy.yaml
    │   ├── mitra.yaml
    │   └── postgres.yaml
    ├── mitra/
    │   └── mitra_config.yaml
    ├── quadlet/
    │   ├── apnet.network
    │   ├── caddy.kube
    │   ├── mitra.kube
    │   └── postgres.kube
    └── secrets/
        └── configmap.yaml

Network

pod間の通信のため

quadlet

  • quadlet/apnet.network
[Network]
NetworkName = apnet

PostgresSQL

config

  • ConfigMapを設定する
    • 本当はSecretsにすべきだが、quadletのkube unitがConfigMapにしか対応していないため
  • secrets/configmap.yaml
    • ユーザー名・パスワード・データベース名を指定してください
apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config
data:
  user: <username> # FIXME
  password: <password> # FIXME
  database: <database> # FIXME
  encoding: "--encoding=UTF8"

kube

  • kube/postgres.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
  labels:
    app: database-pod
  name: database-pod
spec:
  containers:
    - args:
        - postgres
      env:
        - name: POSTGRES_USER
          valueFrom:
            configMapKeyRef:
              name: postgres-config
              key: user
        - name: POSTGRES_PASSWORD
          valueFrom:
            configMapKeyRef:
              name: postgres-config
              key: password
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: postgres-config
              key: database
        - name: POSTGRES_INITDB_ARGS
          valueFrom:
            configMapKeyRef:
              name: postgres-config
              key: encoding
      image: docker.io/library/postgres:latest # FIXME: specific version
      name: postgres
      volumeMounts:
        - mountPath: /var/lib/postgresql
          name: postgres-pvc
        - mountPath: /var/lib/postgresql/18/docker # see: https://hub.docker.com/_/postgres#pgdata
          name: postgres-data-pvc
  volumes:
    - name: postgres-pvc
      persistentVolumeClaim:
        claimName: postgres
    - name: postgres-data-pvc
      persistentVolumeClaim:
        claimName: postgres-data

quadlet

  • quadlet/postgres.kube
[Unit]
Description=postgres

[Kube]
Yaml=/home/$USER/mitra/quadlet/postgres.yaml # FIXME: $USER
Network = apnet
ConfigMap=/home/$USER/mitra/secrets/configmap.yaml # FXIME: $USER

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

[Service]
Delegate = yes

Mitra

config

  • mitraの設定
    • config.example.yamlを参考に設定する
    • データベースの設定はPostgresSQLに合わせて変更する
  • mitra/mitra_config.yaml
# Mitra configuration file
# FIXME: username, password, database
database_url: postgres://<username>:<password>@database-pod:5432/<database>
storage_dir: /var/lib/mitra
web_client_dir: /usr/share/mitra/www
http_host: "0.0.0.0"
http_port: 8383

# Log level (debug, info, warn)
log_level: warn

# List of allowed origins for CORS (in addition to `instance_url`)
# Trailing slashes are not allowed.
# 別のクライアントが使うためクライアントのURLを指定する
# http_cors_allow_allのオプションがあるらしい
http_cors_allowlist:
  - https://your.example.com

# Domain name
instance_uri: your.example.com
instance_title: example
instance_short_description: my instance
# Long description can contain markdown syntax
instance_description: |
  # My instance
  Welcome!

registrations_open: false

kube

apiVersion: v1
kind: Pod
metadata:
  annotations:
    bind-mount-options: /home/$USER/mitra/mitra/mitra_config.yaml:z # FIXME: $USER
  labels:
    app: mitra-pod
  name: mitra-pod
spec:
  containers:
    - image: docker.io/bleakfuture0/mitra:latest
      name: mitra
      volumeMounts:
        - mountPath: /etc/mitra/config.yaml
          name: mitra-config
  volumes:
    - hostPath:
        path: /home/$USER/mitra/mitra/mitra_config.yaml # FIXME: $USER
        type: File
      name: mitra-config

quadlet

  • quadlet/mitra.kube
[Unit]
Description=mitra
After=postgres.service

[Kube]
Yaml=/home/$USER/mitra/kube/mitra.yaml # FIXME: $USER
Network = apnet

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

[Service]
Delegate = yes

ReverseProxy

config

{
	# Change to your email address for ssl certificate generation
	email admin@your.example.com

	# Let's setup reasonable logging defaults and target location
	log {
		level warn
	}
}

https://your.example.com {
	# Enable response body compression
	encode zstd gzip

	# Set HSTS headers to improve security posture
	header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

	# Set request body size maximum (This conincides with attachment size. Increase or decrease as you deem necessary)
	request_body {
		max_size 1MB
	}

	# Explicit reverse proxy with passing the origin host header to the backend properly
	reverse_proxy mitra-pod:8383 {
		header_up Host {http.request.host}
	}

	# Explicit declaration of tls protocols.
	tls {
		protocols tls1.2 tls1.3
	}
}

kube

  • kube/caddy.yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    bind-mount-options: /home/$USER/mitra/conf:z # FIXME: $USER
  labels:
    app: reverseproxy-pod
  name: reverseproxy-pod
spec:
  containers:
    - image: docker.io/library/caddy:latest # FIXME: specific version
      name: caddy
      ports:
        - containerPort: 80
          hostPort: 8080
        - containerPort: 443
          hostPort: 8443
        - containerPort: 443
          hostPort: 8443
          protocol: UDP
      volumeMounts:
        - mountPath: /etc/caddy
          name: caddy-conf
        - mountPath: /data
          name: caddy-data-pvc
        - mountPath: /config
          name: caddy-config-pvc
  volumes:
    - hostPath:
        path: /home/$USER/mitra/conf # FIXME: $USER
        type: Directory
      name: caddy-conf
    - name: caddy-data-pvc
      persistentVolumeClaim:
        claimName: caddy-daa
    - name: caddy-config-pvc
      persistentVolumeClaim:
        claimName: caddy-config

quadlet

  • quadlet/caddy.kube
[Unit]
Description=caddy
After=mitra.service

[Kube]
Yaml=/home/$USER/mitra/kube/caddy.yaml # FIXME: $USER
PublishPort=8080:80/tcp
PublishPort=8443:443/tcp
PublishPort=8443:443/udp
Network = apnet

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

[Service]
Delegate = yes

サービス化する

  • ログアウト後も起動した状態を保つ
sudo loginctl enable-linger <username>
  • .kube.networkファイルを対象のフォルダにコピーして実行
    • .kubeファイルは一つにまとめられるはずだが、Yamlの指定が分けられなかった
    • postgresの起動に時間が少しかかるので分けたほうが良いかも
# podman quadletが読み込むフォルダに.kube, .networkファイルをコピーする
cp ~/mitra/quadlet/* ~/.config/containers/systemd/
# podman v5.6.0以降では`podman quadlet`コマンドを使うことができるが
# 5.4.0ではsystem-generatorを使う
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrun
systemctl --user daemon-reload
systemctl --user start apnet-network.service
systemctl --user start postgres.service
systemctl --user start mitra.service
systemctl --user start caddy.service
  • adminユーザーを作成する
podman pod exec -it mitra-pod-mitra mitra create-account <username> <password> admin

その他

  • podmanまで使う必要ある?
    • オーバーエンジニアリング気味。素直に公式のdpkgファイルをインストールしたほうが楽
      • Podmanのマスコットがかわいいので
  • S3への対応は?

Discussion