ルートレスなPodmanでLaravel環境を構築するハンズオン

投稿日 最終更新日

2024年8月12日~2024年8月15日

ここではルートレスのPodmanを使いLaravel、Apache、MySQL、PHP-FPMの環境を素早く構築する手段を共有します。

ハンズオンっていうやつを目指した記事です。

記事の構成は「【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン」を大変参考にさせていただいております。

対象読者

  • PodmanでLaravel環境を構築したい人

  • ある程度のLAMP、Laravel、コンテナの知識がある人

なぜPodmanなのか

なぜDockerではなくPodmanを扱うのかなんだけど、私の理由はただ1つで、RHEL9でDockerが正式にサポートされていないから

無理やりDockerをダウンロードして使うこともできるんだけど、Podmanが用意されているならそれを使いたいという感じ。

Podmanの利点にはルートレスやデーモンレスが挙げられるけど、Dockerでもルートレスは可能らしいので主な利点はデーモンレスになるのかなと思う。

目標

ルートレスPodmanを使ったLaravel環境の構築と、「podman play kube」コマンドを使った自動pod構築を目指す。

ハンズオン終了時のGitHub

https://github.com/tamakoma1129/podman-laravel-handson

構築環境

最終的なディレクトリ構成は以下になる予定

.
├── apache
 ├── Containerfile
 └── laravel.conf
├── mysql
 ├── db-store # DBのデータを入れる
 ├── Containerfile
 └── my.cnf # 設定ファイル
├── php
 ├── Containerfile
 └── php.ini
├── vite
 └── Containerfile
├── laravel # laravelのディレクトリ
├── .gitignore
└── podman-laravel-handson.yaml # podman play kube用のyamlファイル

動作環境

以下のような動作環境で作成される

分類名前備考
OSAlmalinux9ホストはWindows11でVMで実行
コンテナツールPodman 4.9.4Podman4と5で違うところあるので注意

また、特殊な条件としてルートレスPodmanかつpodman play kubeでのyamlファイルから自動pod生成を目標とする。

ルートレスでPodmanを動かすため、ルートユーザーでのPodman操作をしないように

記事の表記について

自分で打つコマンドは

[linux]$ podman -v

のように表記する。

[linux]の部分が、コマンドの入力環境。
「$」以降が入力コマンド。

ハンズオンする人に向けた注意事項

Podmanのルートレスモードでは気を抜くと権限問題がすぐ発生する。
もし記事の通りやっているのに権限問題が発生する場合、解決しない場合は以下を確認してほしい。

  1. SELinuxやAppArmorがオンになっていないか?
    オンだとボリューム関連で権限問題が発生します。回避方法はありますが、SELinuxに関する知識が乏しいためこの記事ではSELinuxをオフにした状態で進めます。

  2. 中間IDを意識しているか?
    ルートレスモードのPodmanでは中間ID(空のPodを作成するを参照)が重要になります。

  3. 共有フォルダで作業していないか?
    VMなどでWindowsとの共有フォルダを作成している場合、その共有フォルダ内だと権限を適切に設定できずエラーになります。
    共有フォルダ外で作業することを推奨します。

  4. もし解決しないときは
    解決しない場合は、自分で現在のPodmanの状態を確認し理解していくのが大切です。
    本記事の最後にPodmanの状態を確認するコマンド一覧を載せているので確認してみてください。

また、この記事は本番環境の構築は想定していません。

Podmanのインストールとバージョン確認

インストールしていない人は他記事や公式ドキュメントからインストール。

「podman -v」でバージョン確認

[linux]$ podman -v
podman version 4.9.4-rhel

バージョン5だと仕様が異なる可能性が高いので注意。

SELinuxをオフにする

SELinuxがオンだとボリューム関連で権限問題が発生します。
SELinuxをオンで動かしている人には申し訳ないですが、この記事ではオフにした状態で進めて行きます。

回避方法は存在するみたいなので、ハンズオン終了後に試してみてください。

作業ディレクトリの作成

【注意】VMの共有フォルダだと権限問題発生するので避けること

[linux]$ mkdir podman-laravel-handson
[linux]$ cd podman-laravel-handson

最終的なディレクトリ構成は以下になる

.
├── apache
 ├── Containerfile
 └── laravel.conf
├── mysql
 ├── db-store # DBのデータを入れる
 ├── Containerfile
 └── my.cnf # 設定ファイル
├── php
 ├── Containerfile
 └── php.ini
├── vite
 └── Containerfile
├── laravel # laravelのディレクトリ
├── .gitignore
└── podman-laravel-handson.yaml # podman play kube用のyamlファイル

Gitで管理

私はGitHubで管理するけど、ここら辺はお好みでよいと思う。

初回コミット

リモートリポジトリ先は適宜置き換え必要。

[linux] $ echo "# podman-laravel-handson" >> README.md
[linux] $ git init
[linux] $ git add README.md
[linux] $ git commit -m "first commit"
[linux] $ git branch -M main
[linux] $ git remote add origin [リモートリポジトリ先]
[linux] $ git push -u origin main

これ以降のコミットやプッシュはお好みで。

コードの編集について

Linux内でviやnanoを使いコードを書ける人はそちらでおk。

私はWindowsにGitで持ってきてVSCodeで編集する。

[理解大事] 空のPodを作成する

Podとはコンテナが1つ以上入ったグループのこと。
このPodに入ったコンテナ同士は名前空間を共有される

名前空間ってなに? 具体的に何の名前空間が共有されるの? っていう人は
Linux の名前空間が共有されるとはどういうことなのか $ podman pod create —share namespace を通して理解する。
という素敵な記事を参照ください。

今回主に関係するのはnetwork namespaceとuser namespaceの2つ。

ネットワークの名前空間を共有することで、localhostを通じてコンテナ間接続が可能に。
ユーザーの名前空間を共有することで、PodでUID、GIDの名前空間の一元管理が可能になる。

実際にPodを作成するコマンドが以下。

[linux] $ podman pod create --name podman-laravel-handson-pod \
  --userns=auto:uidmapping=0:0:1,uidmapping=1:1:65536,gidmapping=0:0:1,gidmapping=1:1:65536 \
  --publish 8080:80 \
  --replace

--usernsについて

--userns=autoのuidmapping,gidmappingオプションを利用しUID,GIDの範囲を指定している。

使い方はuidもgidも同じで「uidmapping=CONTAINER_UID:HOST_UID:SIZE」だがルートレスの場合は少し異なる

ルートレスの場合は「uidmapping=CONTAINER_UID:INTERMEDIATE_UID:SIZE」である。
ルートレスモードの場合はINTERMEDIATE_ID、つまり中間IDというのを指定する必要がある。

この中間IDは公式ドキュメントにも説明があるが、一応説明もしてみる。

中間ID

ルートフルの場合、Container UIDとHost UIDを紐づけたいときContainer UID → Host UIDで良かった。
ルートレスの場合、Container UIDとHost UIDを紐づけたいときContainer UID → 中間ID → Host UIDという順序で紐づける必要がある。

例えば、コンテナ内でUID0のユーザーをコンテナ外でUID1000にマッピングしたいとする。

ルートフルの場合はuidmapping=0:1000:1となるが、
ルートレスの場合はuidmapping=0:[ホストUID1000と紐づいた中間ID]:1のように指定する必要がある。

この中間IDはUIDの場合「/etc/subuid」で、GIDの場合「/etc/subgid」で利用範囲を指定する。

tamakoma:100000:65536
上の意味は、tamakomaのサブIDをID100000から65536個の連続した範囲で取得するというもの。

もしpodmanをtamakomaというユーザーで実行しているなら、この/etc/subuidまたは/etc/subgidを設定後「podman system migrate」を実行してあげることで中間IDがIDが若い順に作成される

つまり、

中間IDHost ID
1100000
2100001
nn+1

という感じ。

もし、/etc/subuidを

tamakoma:100000:65536
tamakoma:1100:1

のようにするとどうなるだろうか。

この場合も中間IDはIDが若い順に作成されるので以下のようになる。

中間IDHost ID
11100
2100000
3100001

私はこの若い順に作成されることに気づかなくて、かなりの時間を要した。

実際にどのように中間IDが登録されているかは以下のコマンドで確認できる。

[linux]$ podman unshare cat /proc/self/uid_map
         0       1000          1
         1       1100          1
         2     100000      65536

実際に中間UID1→Host UID1100が登録された後に、UID100000以降が登録されていることがわかる。

また、中間ID0に自分自身のIDが登録されていることがわかる。
表示の通り、自分自身のUIDは中間ID0にマッピングされるんだね。

公式ドキュメントには記述が無かったが、GIDも同じ仕様だと思われる。

改めて--usernsについて

ここで改めて先のオプションを見たい。

以下のオプションが指定されている。

  • uidmapping=0:0:1

  • uidmapping=1:1:65536

  • gidmapping=0:0:1

  • gidmapping=1:1:65536

Container ID:中間ID:範囲という指定をするのでまとめると

uidとgidをそれぞれ、ContainerID0→中間ID0のマッピングを1+65536=65537の範囲で行うということになる。

そのため、

  • uidmapping=0:0:65537

  • gidmapping=0:0:65537

でも問題なさそうな感じがするんだけど、これだとどうしてもエラーが発生するので分けている。

この時、/etc/subuidと/etc/subgidは使わなそうなHostIDから65536の範囲をとってもらえればおk。

/etc/subuidと/etc/subgidの記述

私は扱いやすくするため、/etc/subuidも/etc/subgidも100001から65536の範囲を取得する。

viなどで、以下のように記述。
ユーザー名は自身のものに。

tamakoma:100001:65536

こうすれば

中間IDHost ID
0自身のID
1100001
2100002
n100000 + n

となり、かなり扱いやすいと思う。

記述後は

[linux]$ podman system migrate

で変更を反映する。

PHP-FPMコンテナを作成する

Docker公式のPHP-FPMコンテナをベースにカスタマイズする。

ディレクトリ構成は以下のような感じになる。

.
├── php
 ├── Containerfile
 └── php.ini
└── laravel # laravelのディレクトリ

./php/Containerfileの作成

ContainerfileはDockerでいうDockerfileのこと。

[linux]$ mkdir php
[linux]$ touch php/Containerfile

Containerfileの中身は以下を丸々コピペしてください。

FROM docker.io/php:8.1-fpm

# Composerでroot実行を許可する環境変数
ENV COMPOSER_ALLOW_SUPERUSER=1
ENV COMPOSER_HOME=/composer

COPY --from=docker.io/composer:2.6.6 /usr/bin/composer /usr/bin/composer

# git : Laravelをgit pullする為
# unzip : Composerに必要。
# libzip-dev : zipに必要。
RUN apt-get update \
  && apt-get -y install --no-install-recommends \
  git \
  unzip \
  libzip-dev \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

# zip : Laravelのプロジェクト作成に必要。
# pod_mysql : MySQLに必要。
RUN docker-php-ext-install zip pdo_mysql

# docker-php-ext-installはphp.iniを上書きするわけでは無いのでここでおk
COPY ./php.ini /usr/local/etc/php/php.ini

# 権限問題的にwww-data=UID3900として実行。主にlaravel/storage/logs/laravel.logをPHP-FPMが弄るために
USER 3900

上記のContainerfileはLaravelを稼働するために必要最低限の拡張機能しか入っていないので、ハンズオンを完了してから色々弄ることを推奨。

apt-getコマンドは、LinuxのディストリビューションであるDebianのパッケージ管理コマンド。
Docker公式イメージで語尾にalpineとかが付いていなければ基本Debian。

docker-php-ext-installコマンドでは、phpの拡張機能をインストールしている。
勝手にphp.iniなども調整してくれる便利なやつ。

USER 3900はUID3900で実行するということ。
つまり、Host UIDは103900になる。

ルートレスの関係上、PHP-FPMとApacheは同じユーザーで実行したい。
そのため、今回指定するApacheユーザー(www-data)のUID3900に。

./php/php.iniの作成

[linux]$ touch php/php.ini

ハンズオンなので、php.iniの中身は「PHP7.4 ぼくのかんがえたさいきょうのphp.ini」の開発環境版をほぼ丸々参考にさせて頂いた。

開発環境版なので、本番環境では利用しないこと。

エラーログの出力だけ「/dev/stderr」に変更している。
「/dev/stderr」にすることで、Podmanのログに標準エラー出力で表示される。

zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

./laravelの作成

Laravelプロジェクトを入れる為のlaravelディレクトリの作成。

[linux]$ mkdir laravel

build & run

[linux]$ podman build --tag php-fpm-8.1-image ./php

でイメージ名:php-fpm-8.1-imageでビルド。

ビルド後、以下でrunする

podman run --detach \
  --pod podman-laravel-handson-pod \
  --name php-fpm-8.1-container \
  --volume ./laravel:/laravel \
  --workdir /laravel \
  --replace \
  php-fpm-8.1-image
  • podman run --detach
    バックグラウンドで実行

  • --pod podman-laravel-handson-pod
    指定したPod内にコンテナを作成

  • --name php-fpm-8.1-container
    コンテナ名を指定

  • --volume ./laravel:/laravel
    ボリュームの作成

  • --workdir /laravel
    ワーキングディレクトリの指定

  • --replace
    既に同じ名前のコンテナがあれば置き換える

  • php-fpm-8.1-image
    指定したイメージのRun

Run後に以下で詳細を確認。

[linux]$ podman pod ps --ctr-status --ctr-names
POD ID        NAME                        STATUS      CREATED         INFRA ID      NAMES                                     STATUS
7a35bf72df98  podman-laravel-handson-pod  Running     18 minutes ago  f52bacd35083  7a35bf72df98-infra,php-fpm-8.1-container  running,running

PodがRunningになっており、infraコンテナと今作ったphp-fpmコンテナがrunnningになってればおk。

infraコンテナというのは、Pod内で名前空間を共有したりするのに利用されるコンテナ。
infraコンテナはPodを作成すると勝手に作成される。

PHP-FPMコンテナ内に入り確認

中に入り色々確認してみよう。

[linux]$ podman exec -itu root php-fpm-8.1-container bash

というコンテナ内でコマンドを実行するexecコマンドでコンテナ内のrootユーザーにログイン。

PHPのバージョン確認をする

[php-fpm]$ php -v
PHP 8.1.29 (cli) (built: Jul 23 2024 09:14:55) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.29, Copyright (c) Zend Technologies

Composer

[php-fpm]$ composer -V
Composer version 2.6.6 2023-12-08 18:32:26

phpの拡張機能も見てみる

[php-fpm]$ php -m
[PHP Modules]
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]

また、PHP-FPMがUID3900で動いているかも確認する。

[php-fpm]$ apt update && apt install -y procps

でpsコマンドをインストール。
root権限にログインしてないと権限エラーになるので注意。

[php-fpm]$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
3900           1  0.0  1.3  80576 24456 ?        Ss   09:34   0:00 php-fpm: master process (/usr/local/etc/php-fpm.conf)
3900           2  0.0  0.5  80576  9344 ?        S    09:34   0:00 php-fpm: pool www
3900           3  0.0  0.5  80576  9344 ?        S    09:34   0:00 php-fpm: pool www

期待通りな事が分かったので終了。

[php-fpm]$ exit

続いてはApacheコンテナを作成する。
今作ったコンテナやPodはそのままでおk。

Apacheコンテナを作成する

Docker公式のApacheコンテナをベースにカスタマイズする。

ディレクトリ構成は以下のような感じになる。

.
├── apache
 ├── Containerfile
 └── laravel.conf

./apache/Containerfileの作成

[linux]$ mkdir apache
[linux]$ touch apache/Containerfile

中身は以下

FROM docker.io/httpd:2.4.62

RUN usermod -u 3900 www-data

RUN echo "Include conf/extra/laravel.conf" >> /usr/local/apache2/conf/httpd.conf

CMD ["httpd-foreground"]
  • RUN usermod -u 3900 www-data
    www-dataが初期だとUIDが33なので、3900に変更。
    今回の構成ならUID33のままでも良いんだけど、色々増やすとUIDが他のコンテナと衝突することがあったので。

  • RUN echo "Include conf/extra/laravel.conf" >> /usr/local/apache2/conf/httpd.conf
    laravel.confを読み込んでもらう為にhttpd.confにincludeを記述。

  • CMD ["httpd-foreground"]
    初期設定だとバックグラウンドで動いてしまい、コンテナが即座に落ちるのでフォアグラウンドで起動するように。

./apache/laravel.confの作成

PHP-FPMと疎通するのに必要なモジュールを有効化したりなどする。

[linux]$ touch apache/laravel.conf

中身は以下

# php-fpmに必要なモジュール
LoadModule proxy_module modules/mod_proxy.so
LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

# Laravelの.htaccessを読み取る為に必要
LoadModule rewrite_module modules/mod_rewrite.so

# index.phpを読み取る
DirectoryIndex index.php

<VirtualHost *:80>
    ServerName podman-laravel-handson.test
    DocumentRoot "/laravel/public"

    <FilesMatch \.php$>
        SetHandler "proxy:fcgi://localhost:9000"
    </FilesMatch>

    <Directory "/laravel/public">
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog /proc/self/fd/2
    CustomLog /proc/self/fd/1 common
</VirtualHost>

ここら辺は人の環境によって大きく変わるところだと思うので、お好みで。

PHP-FPMとはlocalhostで9000番ポートを通し通信している。

build & run

[linux]$ podman build -t apache-2.4-image ./apache

でビルド。

Runは以下で

[linux]$ podman run -d \
  --pod podman-laravel-handson-pod \
  --name apache-2.4-container \
  --volume ./laravel:/laravel \
  --volume ./apache/laravel.conf:/usr/local/apache2/conf/extra/laravel.conf \
  --workdir /laravel \
  --replace \
  apache-2.4-image

Run後、「podman pod ps —ctr-status —ctr-names」で動いているか確認。

Apacheコンテナの確認

期待通り動いているか確認する。

[linux]$ mkdir laravel/public
[linux]$ echo "<?php phpinfo();" > laravel/public/phpinfo.php

でphpの情報を表示するファイルを作成。

[linux]$ curl http://localhost:8080/phpinfo.php

でアクセス。

長いHTMLが返ってくればおk。

80番ポートでアクセスできるようにする

ここは必要な人のみ。

私はWindows11にVMでLinuxを入れ、そこでコンテナを構築している。
そのため、Windows11からコンテナ内にアクセスできるようにしたい。

既に、Windows11からVMに80番ポートでアクセスできる前提とする。

やることは、

[linux]$ firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=8080

で80番を8080に転送。

[linux]$ sudo firewall-cmd --reload

再起動すれば完了。

Server APIがFPM/FastCGIになってるのとか確認しとくと良いかも。

Laravelをインストールする

まず、テスト用で作ったファイルを削除。

[linux]$ rm -rf laravel/*

つづいて、php-fpmコンテナに入り

[linux]$ podman exec -itu root php-fpm-8.1-container bash

Laravelをインストールする

[php-fpm]$ composer create-project --prefer-dist  laravel/laravel:^10.0 /laravel
[php-fpm]$ php artisan -V
Laravel Framework 10.48.20

Laravelの権限問題とウェルカム画面表示

この状態でLaravelにアクセスしてみると

権限問題でエラーになる。

これは、ApacheとPHP-FPMの実行ユーザーであるwww-dataがlaravelディレクトリに対し適切な権限を持たないから。

一回出て

[php-fpm]$ exit

以下のように権限を与える

[linux]$ sudo chown -R 103900 laravel/storage/
[linux]$ sudo chmod -R a=rX,u+w  laravel/storage/
[linux]$ sudo chown -R 103900 laravel/bootstrap/cache/
[linux]$ sudo chmod -R a=rX,u+w laravel/bootstrap/cache/

laravel/storage/とlaravel/bootstrap/cache/以下の所有者をUID103900=コンテナ内のUID3900=www-dataにし、ファイルは644、ディレクトリは755にしている。

これで

ウェルカム画面が表示できた。

MySQLコンテナを作成する

Docker公式のMySQLコンテナをベースにカスタマイズする。
MySQL公式が提供するコンテナも存在するが最終Updateが1年前だし、Docker公式イメージで今のところ問題が無いので利用していない。

ディレクトリ構成は以下のような感じになる。

.
├── mysql
 ├── db-store # DBのデータを入れる
 ├── Containerfile
 └── my.cnf # 設定ファイル

./mysql/Containerfileの作成

[linux]$ mkdir mysql
[linux]$ touch mysql/Containerfile

でContainerfile作成。

中身は以下。

FROM docker.io/mysql:8.4.2

# mysqlのUIDを3901に。
RUN usermod -u 3901 mysql

ENV MYSQL_DATABASE=laravel \
  MYSQL_USER=tamakoma \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret \
  TZ=Asia/Tokyo

予め環境変数を指定することで、DBやユーザーを作成してくれる。
今回の名前やパスワードは適当なので、お好みに。

./mysql/my.cnfの作成

MySQLの設定ファイルを作成する。
私の技術力的に設定ファイルの詳しい意図の説明はできないのであしからず。

また、ハンズオン程度なら無くても良いと思われる。

[linux]$ touch mysql/my.cnf

中身は本当に最低限わかるものを追加。

[mysqld]
# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs_ks

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4

主には日本語向け文字コードの設定と、タイムゾーンの設定。

./mysql/db-storeの作成

データを保存するためのディレクトリ作成。

[linux]$ mkdir mysql/db-store

また、MySQLの実行ユーザーUID3901がdb-storeを弄れるよう所有者の変更。

[linux]$ sudo chown 103901:103901 mysql/db-store

.gitignoreの作成

mysql/db-storeの中身をGitに載せると面倒なので無視させる。

[linux]$ echo "mysql/db-store/*" >> .gitignore

build & run

[linux]$ podman build -t mysql-8.4-image ./mysql

でビルド。

Runは以下

[linux]$ podman run -d \
  --pod podman-laravel-handson-pod \
  --name mysql-8.4-container \
  --volume ./mysql/db-store:/var/lib/mysql \
  --volume ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf \
  --replace \
  mysql-8.4-image

Run後、「podman pod ps —ctr-status —ctr-names」で動いているか確認。

Laravelの.env設定

MySQLに合わせて.envのDB設定を変更。

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=tamakoma
DB_PASSWORD=secret

MySQLとはローカルホストの3306ポートを利用して通信を行う。

Laravelでマイグレーション

Laravelの設定が終わったらマイグレーションする。

[linux]$ podman exec -itu root php-fpm-8.1-container bash
[php-fpm]$ php artisan migrate

   INFO  Preparing database.

  Creating migration table ............................................................................................................... 88ms DONE

   INFO  Running migrations.

  2014_10_12_000000_create_users_table .................................................................................................. 119ms DONE
  2014_10_12_100000_create_password_reset_tokens_table ................................................................................... 92ms DONE
  2019_08_19_000000_create_failed_jobs_table ............................................................................................ 105ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ................................................................................. 118ms DONE

MySQLの動作確認

マイグレーションが完了したら、新しくターミナルを開きMySQLコンテナ内に入る。

[linux]$ podman exec -it mysql-8.4-container bash

mysqlにログイン。

[mysql]$ mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)

mysql> show tables;
+------------------------+
| Tables_in_laravel      |
+------------------------+
| failed_jobs            |
| migrations             |
| password_reset_tokens  |
| personal_access_tokens |
| users                  |
+------------------------+
5 rows in set (0.01 sec)

しっかりマイグレーションされている。

こちらのターミナルは使うのでそのままで。

データを追加してみる

Laravel経由でデータを追加してみよう。

MySQLじゃない方のターミナルでPHP-FPMコンテナに入る。

[linux]$ podman exec -itu root php-fpm-8.1-container bash
[php-fpm]$ php artisan tinker

でLaravelと対話モードになり、以下を入力

$user = new App\Models\User();
$user->name = 'tamakoma';
$user->email = 'phper@example.com';
$user->password = Hash::make('secret');
$user->save();

trueが返ってくればおk。

MySQLを開いているターミナルで以下のSQLを実行しデータが追加されていればおk

mysql> SELECT * FROM users\G
*************************** 1. row ***************************
               id: 1
             name: tamakoma
            email: phper@example.com
email_verified_at: NULL
         password: $2y$12$1NyHIRzsEe6ovPq4rypqiuDlwdgcrKs0FlMldk6hmQ5qNqmiy21Lm
   remember_token: NULL
       created_at: 2024-08-13 22:49:04
       updated_at: 2024-08-13 22:49:04
1 row in set (0.00 sec)

確認できたらMySQLのターミナルは閉じる。

Viteコンテナを作成する

フロントエンドのビルドファイルをどうやって持ってくるか悩んだ末、コンテナ立ち上げ時に自動で毎回ビルドするようにした。
最適な方法ではないと思うが、私が行った方法を共有する。

これはPodmanのPodにあるinitコンテナという機能を利用している。

ディレクトリ構成は以下のようになる。

.
├── vite
 └── Containerfile

initコンテナについて

簡単に言えば、Pod内にあるコンテナを起動する前に一度だけ起動するコンテナ。

kubernetesのinitコンテナからインスパイアされているみたい。

しかし、k8sと違いPodmanのinitコンテナにはalwaysとonceの2つモードがあり、alwaysはPodを再起動するごとに、onceはPod初回起動時のみ稼働する。

今回は再起動する毎にビルドしてほしいのでalwaysモードで利用する。

./vite/Containerfileの作成

[linux]$ mkdir vite
[linux]$ touch vite/Containerfile

でContainerfile作成。

中身は以下のみ。

FROM docker.io/node:20.16.0-alpine

これだけなので、Containerfileを作らずにcreate時このイメージを直接指定してもおk。

build & create

以下でビルド

[linux]$ podman build -t vite-20.16-image ./vite

initコンテナ特有のオプションを付ける為、createコマンドを利用する。

createする前に、podを停止しておく。

[linux]$ podman pod stop podman-laravel-handson-pod

createコマンドは以下

[linux]$ podman create \
  --pod podman-laravel-handson-pod \
  --name vite-20.16-container \
  --volume ./laravel:/laravel \
  --replace \
  --init-ctr=always \
  --workdir /laravel  \
  vite-20.16-image \
  sh -c "npm install && npm run build"
  • --init-ctr=always
    でalwaysモードのinitコンテナに。

  • sh -c "npm install && npm run build"
    コンテナ起動時にシェルで「npm install && npm run build」を実行。
    実行後、コンテナは勝手に終了する。

Podを起動し動作確認

[linux]$ podman pod start podman-laravel-handson-pod

で起動。

ビルドするので少し時間が掛かる。

Pod起動後ログを確認。

[linux]$ podman logs vite-20.16-container

ログの最後に「✓ built in 697ms」のようにビルド完了の文字があればおk。

yamlファイルの作成

全てのコンテナを作成したので、ここからはこのコンテナ等が入ったPodを自動構築するファイルを作成する。

Podmanの便利機能にkubeコマンドがある。

これはk8s用のyamlファイルの生成や、k8s用yamlファイルからPod作成などをしてくれるコマンド。

今回はこれを利用し、yamlファイルの作成やPodの自動作成を行う。

podman-laravel-handson.yamlの作成

Podを作成した状態でpodman kube generateコマンドを実行するとPodを元にyamlファイルを作成してくれる。

[linux]$ podman pod ps --ctr-status --ctr-names

でviteコンテナ以外がrunningになっており、期待通り動いていることを確認する。

以下でyamlの作成。

[lixnu]$ podman generate kube -f=podman-laravel-handson.yaml podman-laravel-handson-pod

-fでファイル名を指定できる。

実際に自動で作成されたyamlファイルが以下

# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.9.4-rhel
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2024-08-13T23:45:06Z"
  labels:
    app: podman-laravel-handson-pod
  name: podman-laravel-handson-pod
spec:
  containers:
  - args:
    - php-fpm
    image: localhost/php-fpm-8.1-image:latest
    name: php-fpm-8.1-container
    ports:
    - containerPort: 80
      hostPort: 8080
    securityContext:
      runAsNonRoot: true
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    workingDir: /laravel
  - image: localhost/apache-2.4-image:latest
    name: apache-2.4-container
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    - mountPath: /usr/local/apache2/conf/extra/laravel.conf
      name: home-tamakoma-podman-laravel-handson-apache-laravel.conf-host-1
    workingDir: /laravel
  - args:
    - mysqld
    image: localhost/mysql-8.4-image:latest
    name: mysql-8.4-container
    volumeMounts:
    - mountPath: /etc/mysql/conf.d/my.cnf
      name: home-tamakoma-podman-laravel-handson-mysql-my.cnf-host-0
    - mountPath: /var/lib/mysql
      name: home-tamakoma-podman-laravel-handson-mysql-db-store-host-1
  hostUsers: false
  initContainers:
  - args:
    - sh
    - -c
    - npm install && npm run build
    image: localhost/vite-20.16-image:latest
    name: vite-20.16-container
    resources: {}
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    workingDir: /laravel
  volumes:
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/apache/laravel.conf
      type: File
    name: home-tamakoma-podman-laravel-handson-apache-laravel.conf-host-1
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/mysql/my.cnf
      type: File
    name: home-tamakoma-podman-laravel-handson-mysql-my.cnf-host-0
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/mysql/db-store
      type: Directory
    name: home-tamakoma-podman-laravel-handson-mysql-db-store-host-1
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/laravel
      type: Directory
    name: home-tamakoma-podman-laravel-handson-laravel-host-0

御覧の通りボリュームのパスなどが環境依存なので注意。

yamlファイルの修正

上記のyamlファイルそのままでは期待通りに動かないので修正する。

修正したものが以下。
修正箇所には日本語コメントあり。

# Save the output of this file and use kubectl create -f to import
# it into Kubernetes.
#
# Created with podman-4.9.4-rhel
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2024-08-13T23:45:06Z"
  labels:
    app: podman-laravel-handson-pod
  name: podman-laravel-handson-pod
  annotations:
        # initコンテナをalwaysモードに
        io.podman.annotations.init.container.type: "always"
spec:
  containers:
  - args:
    - php-fpm
    # imageは指定せず、ビルドして作成する
    image: php
    name: php-fpm-8.1-container
    ports:
    - containerPort: 80
      hostPort: 8080
    securityContext:
      runAsNonRoot: true
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    workingDir: /laravel
    # imageは指定せず、ビルドして作成する
  - image: apache
    name: apache-2.4-container
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    - mountPath: /usr/local/apache2/conf/extra/laravel.conf
      name: home-tamakoma-podman-laravel-handson-apache-laravel.conf-host-1
    workingDir: /laravel
  - args:
    - mysqld
    # imageは指定せず、ビルドして作成する
    image: mysql
    name: mysql-8.4-container
    volumeMounts:
    - mountPath: /etc/mysql/conf.d/my.cnf
      name: home-tamakoma-podman-laravel-handson-mysql-my.cnf-host-0
    - mountPath: /var/lib/mysql
      name: home-tamakoma-podman-laravel-handson-mysql-db-store-host-1
  hostUsers: false
  initContainers:
  - args:
    - sh
    - -c
    - npm install && npm run build
    # imageは指定せず、ビルドして作成する
    image: vite
    name: vite-20.16-container
    resources: {}
    volumeMounts:
    - mountPath: /laravel
      name: home-tamakoma-podman-laravel-handson-laravel-host-0
    workingDir: /laravel
  volumes:
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/apache/laravel.conf
      type: File
    name: home-tamakoma-podman-laravel-handson-apache-laravel.conf-host-1
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/mysql/my.cnf
      type: File
    name: home-tamakoma-podman-laravel-handson-mysql-my.cnf-host-0
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/mysql/db-store
      type: Directory
    name: home-tamakoma-podman-laravel-handson-mysql-db-store-host-1
  - hostPath:
      path: /home/tamakoma/podman-laravel-handson/laravel
      type: Directory
    name: home-tamakoma-podman-laravel-handson-laravel-host-0
  • io.podman.annotations.init.container.type: "always"
    initコンテナのalwaysモードはk8sにはないPodman特有の機能。
    こういう機能はanotationで指定する。

    onceとalwaysが混在する場合はどうするんだろうと密かに思っている。

  • image: phpなど
    imageを指定してPod作成もできるが、Containerfileの場所を指定してイメージをビルドすることもできる。
    毎回イメージをビルドする方法にすれば、イメージが存在している必要がないのでこの方法に。

podman play kubeでPod作成

podman play kubeでyamlファイルからPodを作成する。

[linux]$ podman play kube podman-laravel-handson.yaml \
  --userns=auto:uidmapping=0:0:1,uidmapping=1:1:65536,gidmapping=0:0:1,gidmapping=1:1:65536 \
  --build \
  --replace

の実行。
--usernsは空Pod作成時と同じ。
--buildを付けることで強制ビルドしている。

実行すると全てのイメージビルドが始まり、ビルドが終わり次第Podが作成され立ち上がる。

起動したら、podman pod ps --ctr-status --ctr-namesで正常に動いているか確認。

[linux]$ podman pod ps --ctr-status --ctr-names
POD ID        NAME                        STATUS      CREATED        INFRA ID      NAMES                                                                                                                                                                                                               STATUS
164238600f75  podman-laravel-handson-pod  Running     3 minutes ago  6d00db0499c6  164238600f75-infra,podman-laravel-handson-pod-vite-20.16-container,podman-laravel-handson-pod-php-fpm-8.1-container,podman-laravel-handson-pod-apache-2.4-container,podman-laravel-handson-pod-mysql-8.4-container  running,exited,running,running,running

のように、vite以外がrunningなら成功。

実際にLaravelにアクセスして表示されるかも見てみる。

問題なさそう。

環境の再構築

※私のGitHubにあるものをそのまま使う場合、yamlのvolumeパスが/home/tamakoma/podman-laravel-handson/になっているため、自分の環境に合わせる必要あり。

環境を再構築できるか確かめよう。

新しいVM環境を構築し確認していく。
新しいVMの環境構築は「私なりのlaravel環境構築」等を見てもらえればと思う。

ここでは、新しいVM環境を構築しユーザー設定やssh設定などを終えた段階から進める。

Podmanのバージョン更新

Podmanのバージョン確認をしたら、少し古かったので新しいものにする。

[linux]$ podman -v
podman version 4.6.1

[linux]$ sudo dnf update podman

[linux]$ $ podman -v
podman version 4.9.4-rhel

GitHubからリポジトリをクローン

gitのインストールをする

[linux]$ sudo dnf install -y git-all

クローンする

[linux]$ git clone https://github.com/tamakoma1129/podman-laravel-handson.git
[linux]$ cd cd podman-laravel-handson/

権限設定

UID103900(www-data)とUID103901(mysql)が適切なフォルダにアクセスできるようにする。

[linux]$ sudo chown -R 103900 laravel/storage/
[linux]$ sudo chmod -R a=rX,u+w  laravel/storage/
[linux]$ sudo chown -R 103900 laravel/bootstrap/cache/
[linux]$ sudo chmod -R a=rX,u+w laravel/bootstrap/cache/
[linux]$ mkdir mysql/db-store
[linux]$ sudo chown 103901:103901 mysql/db-store

/etc/subuidと/etc/subgidの記述

自分のサブIDを指定しておく。

[linux]$ sudo sh -c 'echo "tamakoma:100001:65536" > /etc/subuid'
[linux]$ sudo sh -c 'echo "tamakoma:100001:65536" > /etc/subgid'

sh -cを使う理由は、リダイレクトの「>」にもroot権限を与える為。

設定したら

[linux]$ podman system migrate

で設定適用。

SELinuxの設定

SELinuxを有効化していると権限問題が発生するのでオフにする。

[linux]$ sudo vi /etc/selinux/config

で「SELINUX=permissive」にする。

設定したら再起動。

[linux]$ cd podman-laravel-handson/
[linux]$ getenforce
Permissive

でおk。

podman play kube

Podを構築し起動する

[linux]$ podman play kube podman-laravel-handson.yaml \
  --userns=auto:uidmapping=0:0:1,uidmapping=1:1:65536,gidmapping=0:0:1,gidmapping=1:1:65536 \
  --build \
  --replace

起動後

[linux]$ $ podman pod ps --ctr-status --ctr-names
POD ID        NAME                        STATUS      CREATED             INFRA ID      NAMES                                                                                                                                                                                                               STATUS
1c46df5f536d  podman-laravel-handson-pod  Running     About a minute ago  155daa1eb5ed  1c46df5f536d-infra,podman-laravel-handson-pod-vite-20.16-container,podman-laravel-handson-pod-php-fpm-8.1-container,podman-laravel-handson-pod-apache-2.4-container,podman-laravel-handson-pod-mysql-8.4-container  running,exited,running,running,running

のようにvite以外がrunningならおk。

ファイアウォールの設定

80番ポートを開き、80番ポートを8080番に転送。

[linux]$ sudo firewall-cmd --permanent --add-service=http
[linux]$ sudo firewall-cmd --permanent --zone=public --add-forward-port=port=80:proto=tcp:toport=8080
[linux]$ sudo firewall-cmd --reload

これで接続してみると……

のようにエラーになる。

これはvendorフォルダ以下が生成されていないから。

composer installして生成しよう。

composer install

composer installしてvendor内のデータを作る。

[linux]$ podman exec -itu root podman-laravel-handson-pod-php-fpm-8.1-container bash

中に入ったらcomposer installする。

[php-fpm]$ composer install

.envの編集

composer installが終わっても.envの編集ができていないのでやっていく。

[php-fpm]$ cp .env.example .env

で写した後

[php-fpm]$ php artisan key:generate

でキー生成。

これでアクセスすれば

表示された!

MySQL関連

マイグレーションとstorageのシンボリックリンクをやっておく。

[php-fpm]$ php artisan migrate
[php-fpm]$ php artisan storage:link

問題なく実行できたらLaravel環境の完成だ!

お疲れ様でした。

Podmanを初期化したいとき

※Podmanやその関係を初期化するので取り返しがつかないことが発生する場合があります。バックアップしてから臨むことが好ましいです。

色々弄っていたらPodmanでファイルの破損? が発生しPodmanコマンドが何も動かなくなってしまったことがある。

具体的には以下のようなエラー

Error: loading primary layer store data: 1 error occurred: * deleting layer "9aee9bd805f04b0209d407b44cf2a278b074904d78fb60bb4ac062eb1e2652d1": unlinkat /home/tamakoma/.local/share/containers/storage/overlay/9aee9bd805f04b0209d407b44cf2a278b074904d78fb60bb4ac062eb1e2652d1/diff/etc: permission denied

適当なpodmanコマンドを実行しても以上のエラーになり、podmanコマンドがそもそも動かなくなってしまったので、初期化を試みた。

まず、何故かpermission deniedになっているところがあるので

sudo chown -R tamakoma /home/tamakoma/.local/share/containers/storage/
sudo chmod -R a=rX,u+w /home/tamakoma/.local/share/containers/storage/

で権限変更。

体感5分くらい掛かった気がする。

その後、

podman stop $(podman ps -aq)
podman rm $(podman ps -aq)

で現在あるコンテナをすべて削除。

続いて、

podman rmi $(podman images -aq)

でイメージの削除。

ここで外部のコンテナとイメージがつながっていて削除できないと言われたら

docker rm $(podman ps  --external -q)

でコンテナ削除してから先のコマンド。

強制削除してくれと書いてあったら-f付けて削除。

つづいて、ボリュームとネットワークの削除。

podman volume rm $(podman volume ls -q)
podman network rm $(podman network ls -q)

最後にpodman systemを使い

podman system prune -a --force --volumes
podman system prune --external

を実行。

これで一通り初期化が完了したと思われる。

さらに踏み込んだPodmanの初期化

以上のことをしても、どうしても原因不明の権限エラーになってしまう。

これ、本当に憶測でしかないんだけど、何かしらのデータが破損または残留していてPodmanが理想の挙動をしてくれていない。
実際に名前空間が謎の配分されてたり、よくわかんない

W: Could not open file '/var/log/apt/term.log' - OpenLog (75: Value too large for defined data type)

というエラー? 警告? が発生したり、謎の権限エラーが発生したりする。

私はこのよくわからないエラーと2日の間戦っている。時間的には10時間ほど。

ネット上に落ちていないエラーが多いし、ChatGPTに聞いてもふんわりした回答しか貰えない。

こういう時は

rm -rf ~/.config/containers ~/.local/share/containers

で設定データ、コンテナ・イメージデータなど、全てを根本から削除する方法もあるみたい。

これはStackoverflowから得た情報。

かなり踏み込んだリセットなので、最終手段にどうぞ。
バックアップを忘れずに。
私はこれで、理想の挙動をしてくれるようになった。
マジでありがとうStackoverflowの人。

Podmanのおすすめコマンド

私が実際によく使っていたコマンドを挙げて終わります。

  • Linuxに設定されているサブIDを確認する
    cat /etc/subuid
    cat /etc/subgid

  • 現在ルートレスでPodmanを実行しているか
    podman info | grep -i rootless

  • Podmanに認識、マッピングされている名前空間を確認する
    podman unshare cat /proc/self/uid_map
    podman unshare cat /proc/self/gid_map

  • Podmanの様々な情報を確認
    podman info

  • 特定のコンテナのマッピングなどの情報を確認する
    podman inspect [コンテナidまたはname]

  • 特定のコンテナのPIDを確認する
    podman inspect [コンテナidまたはname] --format '{{.State.Pid}}'

  • 特定のコンテナの実際に使われているマッピングを確認する
    cat /proc/[container_pid]/gid_map

  • 現在のPodとそのPodに属するコンテナの稼働状況を確認する
    podman pod ps --ctr-status --ctr-names

もしご指摘があれば

もしこの記事や方法にご指摘があればGitHubのissueなどで伝えていただければ幸いです。