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
構築環境
-
Laravel: 10.x
-
Apache: 2.4.62
-
MySQL: 8.4.2
-
PHP-FPM: 8.1-fpm
-
node: 20.16.0-alpine
-
備考: viteのビルドで利用
最終的なディレクトリ構成は以下になる予定
.
├── 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ファイル
動作環境
以下のような動作環境で作成される
分類 | 名前 | 備考 |
OS | Almalinux9 | ホストはWindows11でVMで実行 |
コンテナツール | Podman 4.9.4 | Podman4と5で違うところあるので注意 |
また、特殊な条件としてルートレスPodmanかつpodman play kubeでのyamlファイルから自動pod生成を目標とする。
ルートレスでPodmanを動かすため、ルートユーザーでのPodman操作をしないように。
記事の表記について
自分で打つコマンドは
[linux]$ podman -v
のように表記する。
[linux]の部分が、コマンドの入力環境。
「$」以降が入力コマンド。
ハンズオンする人に向けた注意事項
Podmanのルートレスモードでは気を抜くと権限問題がすぐ発生する。
もし記事の通りやっているのに権限問題が発生する場合、解決しない場合は以下を確認してほしい。
-
SELinuxやAppArmorがオンになっていないか?
オンだとボリューム関連で権限問題が発生します。回避方法はありますが、SELinuxに関する知識が乏しいためこの記事ではSELinuxをオフにした状態で進めます。 -
中間IDを意識しているか?
ルートレスモードのPodmanでは中間ID(空のPodを作成するを参照)が重要になります。 -
共有フォルダで作業していないか?
VMなどでWindowsとの共有フォルダを作成している場合、その共有フォルダ内だと権限を適切に設定できずエラーになります。
共有フォルダ外で作業することを推奨します。 -
もし解決しないときは
解決しない場合は、自分で現在の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
-
podman pod create
でPodの作成 -
--name podman-laravel-handson-pod
でPodの名前付け -
--userns=auto:uidmapping=0:0:1,uidmapping=1:1:65536,gidmapping=0:0:1,gidmapping=1:1:65536
で名前空間モードの指定を行っている。
具体的には後述。
ユーザー名前空間が共有されるため、全てのPod内コンテナでこの設定が適用される。 -
--publish 8080:80
8080ポートを公開し、コンテナ内の80番に転送。
8080ポートの理由は、ルートレスではウェルノウンポートを指定できないから。
ネットワーク名前空間が共有されるため、全てのPod内コンテナでこの設定が適用される。 -
--replace
もし既に同じ名前のPodがあったら置き換える
--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が若い順に作成される。
つまり、
中間ID | Host ID |
1 | 100000 |
2 | 100001 |
… | … |
n | n+1 |
という感じ。
もし、/etc/subuidを
tamakoma:100000:65536
tamakoma:1100:1
のようにするとどうなるだろうか。
この場合も中間IDはIDが若い順に作成されるので以下のようになる。
中間ID | Host ID |
1 | 1100 |
2 | 100000 |
3 | 100001 |
… | … |
私はこの若い順に作成されることに気づかなくて、かなりの時間を要した。
実際にどのように中間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
こうすれば
中間ID | Host ID |
0 | 自身のID |
1 | 100001 |
2 | 100002 |
… | … |
n | 100000 + 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などで伝えていただければ幸いです。