TORICO Tech ブログhttps://tech.torico-corp.com/blog/2024-03-29T02:07:00+00:00株式会社TORICO 技術開発チームのブログdnfでphpのバージョンを指定してモジュールでインストールする方法2024-03-11T04:10:39+00:002024-03-28T16:31:05+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/dnf%E3%81%A7php%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%82%92%E6%8C%87%E5%AE%9A%E3%81%97%E3%81%A6%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB%E3%81%A7%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95/<div class="page-body">
<h1 class="" id="8f586e2c-dbac-4be0-8dab-d920a36f2e26">dnfとは</h1>
<p class="" id="75d5d7d9-9037-4acf-ab00-23880eb4573c">dnf は yum の後継となるパッケージ管理コマンドです。</p>
<p class="" id="ad98de17-765e-4daa-b6a2-cb049b109ea8">Dandified Yum の略で yum から派生して速度の改善がされていたり、モジュールの切り替えの簡易化などの機能が追加されています。</p>
<p class="" id="0bc05ffc-bf7f-4221-96f3-652962383022"></p>
<h1 class="" id="19152f23-2bbc-4c57-8008-82977cd6ba2d">実際に使ってみる</h1>
<p class="" id="51eefa46-e4c3-44bd-a33e-d5033355419e">今回はRedHat UBIで環境を構築して、新しいバージョンのphpを使えるようにしました。</p>
<p class="" id="f0d183cd-a8a5-4589-aa8f-6fa535e031b8">そのままのリポジトリだとPHPのバージョンが<code>7</code>なので、dnfを使用して<code>8</code>系をインストールします。</p>
<h2 class="" id="0e23d010-4ff9-457a-83c9-6183c54271e9">dockerコンテナを準備する</h2>
<p class="" id="205d55bf-1aad-4ab1-8e52-cc9504162139">docker hub から redhat/ubi8 のコンテナを取得</p>
<pre class="code" id="aca5f1d0-d98a-4bdd-ae28-84cd4fa4c9b8"><code class="language-Bash">docker pull redhat/ubi8:latest</code></pre>
<p class="" id="cf20988b-67ef-4ada-8390-627f45442011">コンテナを起動してシェルスクリプトに入る</p>
<pre class="code" id="7228f13c-aac7-48b2-b034-fc1df9c863f4"><code class="language-Bash">docker run -it redhat/ubi8:latest</code></pre>
<h2 class="" id="916ebb8c-bedb-4730-abb7-22c5e80dc820">dnfを使ってパッケージの確認</h2>
<p class="" id="bc38515b-0832-42da-9960-9c07b4f633ab">phpがインストールされているか確認する</p>
<pre class="code" id="13882d88-edec-474b-ba9a-8c65ba06a76f"><code class="language-Bash">dnf list installed</code></pre>
<p class="" id="ee296f40-11ee-4df9-aba9-80c56b3edbdf">インストールされていなかったので、いまのリポジトリで使えるPHPを確認する</p>
<pre class="code" id="f7d5867d-503f-4e6c-9cdd-10e815600156"><code class="language-Bash">dnf list php*</code></pre>
<p class="" id="29e9b3f8-690a-4c64-a135-5e04d99f5ece"><code>7.2.24</code> はありますが、新しいバージョンである<code>8</code>はありませんでした。</p>
<p class="" id="22f5ea82-2fc5-4fc8-bdf3-11c41f0f780c"><code>8</code>をインストールをするためにリポジトリを追加します。</p>
<p class="" id="c109b431-dca3-4b3c-8166-cc28b0e0ee44">現在のリポジトリを確認</p>
<pre class="code" id="dcc810c5-29b7-4d71-9657-d5034c923d1f"><code class="language-Bash">dnf repolist --all</code></pre>
<p class="" id="a8f3a344-604a-4dbe-9ee8-7bfa2c46374e">今回使いたいリポジトリは <code>epel</code> と <code>remi</code>。<br/>現在のリポジトリには含まれていなかったのでリポジトリを追加します。</p>
<p class="" id="e1c20d76-018a-4976-8813-59ef8f248a6e">epelリポジトリを追加。</p>
<pre class="code" id="9c2eee01-8514-4e42-addf-78429464d93a"><code class="language-Bash">dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm</code></pre>
<p class="" id="7e31af59-0e3a-468b-81ea-486b4c5c2a0f">remiリポジトリを追加。</p>
<pre class="code" id="9f790553-b78a-4175-8701-3ad686ad9013"><code class="language-Bash">dnf install -y https://rpms.remirepo.net/enterprise/remi-release-8.rpm</code></pre>
<p class="" id="9f15b63e-95d5-4ee2-a4d4-e6d583d24f60">改めて追加したリポジトリも含めて使えるPHPを確認する</p>
<pre class="code" id="6bc090f9-ec82-4e67-bf36-51d5e4da5531"><code class="language-Bash">dnf list php*</code></pre>
<p class="" id="0284d8ec-8357-44a9-8a2f-877e75f1cc67">結果</p>
<pre class="code" id="b6ec4d99-4214-477b-a992-4ba1a9923042"><code class="language-Bash">5.6
7.2
7.3
7.4
8.0
8.1
8.2
8.3</code></pre>
<p class="" id="846382c7-37c5-4fc1-a017-3d6498dbce56"><code>8.3</code>までのバージョンが使えるようになりました。</p>
<h2 class="" id="4e117051-f14b-497e-a8b7-0e29e46f7aee">バージョンを指定してモジュール形式でインストール</h2>
<p class="" id="f39cebb9-edf6-46ab-8e38-6b1221762fc4">ここで yum であればそのまま</p>
<pre class="code" id="f4a0f4b7-ca4b-4d3f-8e8c-f62a6f745210"><code class="language-Bash">yum install php83</code></pre>
<p class="" id="04530c17-57da-4aac-a01b-cec07bd01c7c">のコマンドを入力してパッケージを追加しますが、追加したremiリポジトリからのインストールだと通常時と違いremiフォルダにインストールされます。</p>
<p class="" id="8439d527-e4d8-4ff8-b9dc-5faf70a53e15">今後のことを考えるとフォルダが違うのはあまり望ましくありません。</p>
<p class="" id="a2d55893-1d4f-47cf-ac7b-b5994c283386">そこでdnf にはモジュールとして、パッケージのバージョンを指定する方法があります。</p>
<p class="" id="c6ecf434-624f-43eb-b427-bc1772d7301f">モジュールとパッケージのリストを表示</p>
<pre class="code" id="741df4f4-b3b3-48a4-b119-9c0203ae6dfe"><code class="language-Bash">dnf module list php</code></pre>
<p class="" id="746857b2-d22d-4d9f-884e-9ecb0b3b02d8">下記のようなリポジトリとパッケージのリストが表示されます。<br/>現在は<br/><code>7.2</code>に<code>[d]</code>がついているので、基本リポジトリの<code>7.2</code>がデフォルトとなっています。</p>
<pre class="code" id="5f95609d-e6d1-4907-9272-6396c03b9717"><code class="language-Bash">Remi's Modular repository for Enterprise Linux 8 - aarch64
Name Stream Profiles Summary
php remi-7.2 common [d], devel, minimal PHP scripting language
php remi-7.3 common [d], devel, minimal PHP scripting language
php remi-7.4 common [d], devel, minimal PHP scripting language
php remi-8.0 common [d], devel, minimal PHP scripting language
php remi-8.1 common [d], devel, minimal PHP scripting language
php remi-8.2 common [d], devel, minimal PHP scripting language
php remi-8.3 common [d], devel, minimal PHP scripting language
Red Hat Universal Base Image 8 (RPMs) - AppStream
Name Stream Profiles Summary
php 7.2 [d] common [d], devel, minimal PHP scripting language
php 7.3 common [d], devel, minimal PHP scripting language
php 7.4 common [d], devel, minimal PHP scripting language
php 8.0 common [d], devel, minimal PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled</code></pre>
<p class="" id="59a58a1e-99e5-4b2c-a773-0348d0de95e9">remiリポジトリの<code>8.3</code>のモジュールをインストールします。</p>
<pre class="code" id="34b59bae-c585-497d-bf10-e9b8af3f29ae"><code class="language-Bash">dnf module install php:remi-8.3</code></pre>
<h2 class="" id="fdad330a-9712-485f-95d6-bb36aec20776">インストール結果の確認</h2>
<p class="" id="fe279a31-64e1-40aa-a263-f961bf00e7df">現在のモジュールとパッケージのリストを表示</p>
<pre class="code" id="dcbd2e20-81eb-40cf-8cbb-6db7101e7c81"><code class="language-Bash">dnf module list php</code></pre>
<p class="" id="3ee672c2-d12e-4ee0-b46e-179479fd821e">remiリポジトリの<code>8.3</code>に<code>[e][i]</code>がついていいます。</p>
<pre class="code" id="fc06b236-62c3-4d5b-916f-95bd4c089329"><code class="language-Bash">Remi's Modular repository for Enterprise Linux 8 - aarch64
Name Stream Profiles Summary
php remi-7.2 common [d], devel, minimal PHP scripting language
php remi-7.3 common [d], devel, minimal PHP scripting language
php remi-7.4 common [d], devel, minimal PHP scripting language
php remi-8.0 common [d], devel, minimal PHP scripting language
php remi-8.1 common [d], devel, minimal PHP scripting language
php remi-8.2 common [d], devel, minimal PHP scripting language
php remi-8.3 [e] common [d] [i], devel, minimal PHP scripting language
Red Hat Universal Base Image 8 (RPMs) - AppStream
Name Stream Profiles Summary
php 7.2 [d] common [d], devel, minimal PHP scripting language
php 7.3 common [d], devel, minimal PHP scripting language
php 7.4 common [d], devel, minimal PHP scripting language
php 8.0 common [d], devel, minimal PHP scripting language
Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled</code></pre>
<p class="" id="77b218c4-b7af-4ce9-9c4e-6bcde82ec305">バージョンを表示。</p>
<pre class="code" id="c4c1afeb-23ea-4997-be08-a402e56c6276"><code class="language-Bash">php -v</code></pre>
<p class="" id="cb6836cb-54d4-4859-a9e0-182e38449bc0"><code>8.3</code>がインストールされてることが確認できます。</p>
<pre class="code" id="8e04ec44-699c-41e4-b998-38155a4b69ec"><code class="language-Bash">PHP 8.3.3 (cli) (built: Feb 13 2024 15:41:14) (NTS gcc aarch64)
Copyright (c) The PHP Group
Zend Engine v4.3.3, Copyright (c) Zend Technologies</code></pre>
<h1 class="" id="72c25453-5b2f-4b4d-8537-f73aa5fbb8fa">dnfのメリット</h1>
<p class="" id="de7cc438-4347-4165-9be6-68ac73ff3307">phpだけではなくapache、nginx、php-fpmなど他のパッケージでもモジュールでインストールすることができます。</p>
<p class="" id="99026019-1748-4071-886f-f6308bd1ba63">本番環境の構築だけでなく、他の環境をPCでローカル環境として再現する場合などdnfは便利なパッケージ管理コマンドです。</p>
<p class="" id="234fdd23-6105-43bb-bd2c-f9e1d368801e">また、モジュール形式でインストールすると有効なバージョンの切り替えもできるらしいので、これはありがたい機能。</p>
<pre class="code" id="ea9160fb-f50c-4b9e-9f0c-b37e4d8098ad"><code class="language-Bash">dnf module switch-to module:version</code></pre>
<p class="" id="31a45295-5e05-499a-8df2-a89ab912aea9"></p>
</div>ubuntuのインプレースアップデートの手順書2023-11-15T03:19:09+00:002024-03-29T01:18:26+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/ubuntu%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%82%B9%E3%82%A2%E3%83%83%E3%83%97%E3%83%87%E3%83%BC%E3%83%88%E3%81%AE%E6%89%8B%E9%A0%86%E6%9B%B8/<h1 class="page-title">Ubuntu をインプレースアップデート<span>した</span></h1>
<div class="page-body">
<p>バージョンが <code>18</code> のUbuntuで動作している社内サーバーが残っていたのアップデートしました。</p>
<p class="" id="50213dd9-49b3-4f86-bd00-3f44461f080c">どうせなら <code>22</code> にしたいのですが、<code>18 → 22</code> のようなバージョンをとばしたアップデートはできません。</p>
<p class="" id="8dfa08e2-8604-483f-8bda-0a453ef430f9"><code>18 → 20</code>、<code>20 → 22</code>と1段階ずつアップデートしていきます。</p>
<p class="" id="65c881b1-d4bb-42f3-a9bb-10b67cc69650"></p>
<p class="" id="59dd4cb5-889a-42b1-90e3-482b8b8c6f84">現在のバージョンの確認</p>
<pre class="code" id="3887fdfd-edbc-464c-9c74-dd9b0f891ddc"><code>cat /etc/os-release</code></pre>
<p class="" id="fcfe8c24-c1fc-43ff-aa86-42a3cee1bd66">アップグレード可能なバージョンの確認</p>
<pre class="code" id="c3fa86d6-8db4-4804-a765-6d541c035583"><code>sudo do-release-upgrade -c</code></pre>
<p class="" id="793443b7-7996-4d4c-827b-463de4d066d5">インストール済みのaptパッケージの更新と不要なパッケージ、キャッシュのされているdepファイルの削除。エラーがでた場合は個別にコマンドを実行します</p>
<pre class="code" id="74da7fec-3d26-4427-8c21-d9dfac2b61f9"><code>sudo apt update -y && sudo apt upgrade -y && sudo apt autoremove -y && sudo apt autoclean -y</code></pre>
<p class="" id="f0103604-2d0a-4c87-8739-316272dd8bd7">アップデートが残っていて、適用されたならば一度再起動しておきます</p>
<pre class="code" id="42bfcc06-ee0b-4db4-917b-ea4c06f9e969"><code>sudo reboot</code></pre>
<p class="" id="c4e455dd-f54f-4c23-a585-a6f469a38e6a">Ubuntu をアップグレードするコマンドを実行</p>
<pre class="code" id="1157de81-4d9f-4a06-bcd7-c3cabe3e1425"><code>sudo do-release-upgrade</code></pre>
<p class="" id="5c187211-0b88-43e6-8e60-319e0dd24ac3"></p>
<p class="" id="23401f78-e558-4df7-af29-51ffa4c8e5f4">以下はアップグレード時の質問事項</p>
<p class="" id="1d8ab659-769a-43e4-b877-a3a49fd606a1">ssh接続の追加ポートとして1022ポートを使用しできるようにするか?という質問は<code>yes</code><br/> アップデート中のトラブル時に再接続のために使用できる。<br/>その後に表示されるコマンドはメモしておく。</p>
<pre class="code code-wrap" id="7e5dd483-c49d-4b46-9afb-d46108165815"><code>If you continue, an additional ssh daemon will be started at port
'1022'.
Do you want to continue?
Continue [yN]</code></pre>
<p class="" id="7ca1b7d8-fcb2-4ba6-8360-370f829c2887">ubuntuにパッケージリポジトリに含まれていないサードパーティ製のアプリの無効化の通知。 <br/>これは通知のみなので<code>enter</code></p>
<pre class="code code-wrap" id="d0008c3c-3048-4717-ae2a-404743e85f7f"><code>Third party sources disabled</code></pre>
<p class="" id="906767f7-4bef-405a-a972-a93cbe7c0619">OSのバージョンに合わせてパッケージをアップグレードを実行してよいか?<br/>パッケージの詳細を確認したい場合は<code>d</code>、進めるには<code>yes</code></p>
<pre class="code code-wrap" id="791d2b54-0e66-4026-933d-64265a6f8e15"><code>Do you want to start the upgrade?
〜更新されるパッケージの一覧の表示〜
Installing the upgrade can take several hours. Once the download has
finished, the process cannot be canceled.
Continue [yN] Details [d]</code></pre>
<p class="" id="993cfa4d-c41f-4458-a7fa-c7778c2f9f8d">再起動する必要があるパッケージがインストール、更新された際に自動で再起動してよいか?<br/> 基本は<code>OK</code>で</p>
<pre class="code code-wrap" id="a0d82227-6535-4e6e-acdb-a146ca8c232c"><code>There are services installed on your system which need to be restarted when certain libraries, such as libpam, libc, and
libssl, are upgraded. Since these restarts may cause interruptions of service for the system, you will normally be prompted
on each upgrade for the list of services you wish to restart. You can choose this option to avoid being prompted; instead,
all necessary restarts will be done for you automatically so you can avoid being asked questions on each library upgrade.
Restart services during package upgrades without asking?</code></pre>
<p class="" id="4bfb0378-09fc-4209-a3c1-15f6f3c078f5">LXDをアップグレードするか? <code>yes</code><br/> 必要がない場合はLXDに関する質問は聞かれない</p>
<pre class="code code-wrap" id="3786c279-f040-4865-aad7-1bec6dfbee39"><code>Upgrade to the LXD snap
Starting with LXD 3.1, all new releases of LXD are only available to Ubuntu users through the snap package.
This package update will transition your system over to the snap by installing it and then running an automated migration
tool.
As part of this upgrade, all containers will briefly be shutdown and brought back up. Before continuing, make sure that you
are ready for this downtime.</code></pre>
<p class="" id="313d59fb-f48b-41e7-a83a-26b35b710efd">LXDのバージョンをどうするか?<br/> 基本は新しいバージョンで。今回の場合は<code>4.0</code> を選択</p>
<pre class="code code-wrap" id="3e6afee9-8698-4b85-9f71-770fae48e197"><code>The LXD project puts out monthly feature releases which while backward compatible at an API and CLI level, will contain
some behavior change and potentially require manual intervention during an upgrade.
In addition to those, every 2 years a LTS release is made which comes with 5 years of support through frequent bugfix-only
releases.
The LXD team recommends you pick "4.0" for production environments and use "latest" if you're interested in getting the
latest LXD features.</code></pre>
<p class="" id="4f6610b4-c544-4e97-9424-df76cfe29d15">構成ファイル <code>sshd_config</code> をどうするか?<br/>現在の設定をそのまま使用するので<code>keep the local version currently installed</code> を選択</p>
<pre class="code code-wrap" id="bf8ee4f2-e662-4489-a139-2175967a7c02"><code>A new version (/tmp/file4mEdmb) of configuration file /etc/ssh/sshd_config is available, but the version installed
currently has been locally modified.
What do you want to do about modified configuration file sshd_config?
</code></pre>
<p class="" id="dc7df46d-214c-4f37-990b-00f442bb5826">アップグレード開始 終了後に古いパッケージの削除をするか?<br/> 古いパッケージは不要なので<code>yes</code></p>
<pre class="code code-wrap" id="76e6c9b3-d3ff-47b9-bc14-42392c0c0f18"><code>Reading package lists... Done
Building dependency tree
Reading state information... Done
Searching for obsolete software
Reading state information... Done
Remove obsolete packages?</code></pre>
<p class="" id="33852c91-ba11-458a-b7b5-60c400a7b097">アップデートの完了<br/> 再起動するか?はもちろん<code>yes</code></p>
<pre class="code code-wrap" id="122ea31b-e688-415e-96dd-c52c6d9ec1f6"><code>System upgrade is complete.
Restart required
To finish the upgrade, a restart is required.
If you select 'y' the system will be restarted.
Continue [yN]</code></pre>
<p class="" id="af030e66-97f5-438a-9b7e-31ddcbf6f767">再起動後にサーバーに接続して現在のバージョンを確認する</p>
<pre class="code code-wrap" id="fe98c69f-d61c-4196-8fa1-4ff9ef5e21b8"><code>cat /etc/os-release</code></pre>
<pre>PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
</pre>
<p class="" id="5fc333c8-0ed1-4e80-a6cc-4f22bbeb9eff">バージョンが上がっていることを確認。<br/> 一通り操作して動作することを確認。<br/> 必要なパッケージがあれば追加でインストールしてアップデートは完了。<br/> 今回は<code>18 → 22</code>に上げたのでもう一度同じ操作を行なっています。</p>
<p class="" id="1e85ee94-1f8d-4281-afaa-28ad8d851b9c"></p>
<p class="" id="94ec3839-0baf-42c0-806b-b93d0c649bd0"><strong>注意ポイントその1</strong></p>
<p class="" id="0137a543-c002-4300-9e44-c6906248ee70">ubuntu 22 にfuseをインストール、更新するとシステムが壊れる可能性があるのでインストールしない。<code>N</code>を選択。</p>
<pre class="code" id="6aaec1cb-c6e1-41c0-917c-175dd4c2a80d"><code>Configuration file '/etc/fuse.conf'
==> Modified (by you or by a script) since installation.
==> Package distributor has shipped an updated version.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** fuse.conf (Y/I/N/O/D/Z) [default=N] ?</code></pre>
<p class="" id="a8fe9c49-8a93-4d78-9128-ac1e3e0b2afe">参照ページ FUSE</p>
<p class="" id="4bf55a60-8243-4aac-b5e1-13d9430bb9ab"><a href="https://github.com/AppImage/AppImageKit/wiki/FUSE">https://github.com/AppImage/AppImageKit/wiki/FUSE</a></p>
<p class="" id="72d05d0a-3467-4f6c-a19e-015b07220bc6">22に最新版をインストールすると壊れますとか物騒すぎる。</p>
<pre class="code" id="23f242eb-410a-49a9-9bb7-cf4b71e11dbd"><code>Warning: While libfuse2 is OK, do not install the fuse package as of 22.04 or you may break your system</code></pre>
<p class="" id="296e3f7c-4856-4c72-9060-4f879ebe4142"></p>
<p class="" id="94ec3839-0baf-42c0-806b-b93d0c649bd0"><strong>注意ポイントその2</strong></p>
<p class="" id="872284c1-b3b7-4043-b40c-b13d944c2f7c">メール認証の設定。<br/> 現在の設定をキープする。<code>N</code>を選択。</p>
<pre class="code" id="0479063c-da15-4a06-ae16-5ac8bda83f5a"><code>Configuration file '/etc/default/saslauthd'
==> Modified (by you or by a script) since installation.
==> Package distributor has shipped an updated version.
What would you like to do about it ? Your options are:
Y or I : install the package maintainer's version
N or O : keep your currently-installed version
D : show the differences between the versions
Z : start a shell to examine the situation
The default action is to keep your current version.
*** saslauthd (Y/I/N/O/D/Z) [default=N] ?</code></pre>
<p class="" id="b1aa2b03-b9e4-4d19-8e86-35389016c110"></p>
</div>docker開発環境ですべてのログをFluentdに集約する2023-09-29T04:29:16+00:002024-03-28T14:48:35+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/docker%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%81%A7%E3%81%99%E3%81%B9%E3%81%A6%E3%81%AE%E3%83%AD%E3%82%B0%E3%82%92fluentd%E3%81%AB%E9%9B%86%E7%B4%84%E3%81%99%E3%82%8B/<div>dockerの開発環境は便利なのですが、トラブルの発生時にコンテナごとにエラーを確認することになるので、そこは少し不便です。 なので、そこの利便性を上げるためにFluentdを使います。 今回はnginx + pfhp-fpm + fluentdの構成でログを集約してみます。</div>
<h4>ディレクトリ構造とファイルの配置の予定</h4>
<pre> my-app/
├─dokcer/
│ ├─nginx/
│ │ ├─nginx.conf
│ │ └─default.conf
│ ├─php-fpm/
│ │ ├─php-extends.ini
│ │ └─log.conf
│ └─fluentd/
│ └─fluent.conf
└─docker-compose.yml
</pre>
<h4>ファイルの作成</h4>
<h5>nginx.conf</h5>
<div>access_log、error_logを設定。 php-fpmとの接続を安定させるためにfastcgi_connect_timeout、fastcgi_read_timeout、fastcgi_send_timeoutを設定。</div>
<pre> user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
sendfile on;
#tcp_nopush on;
keepalive_timeout 300;
send_timeout 300;
fastcgi_connect_timeout 300;
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
</pre>
<h5>default.conf</h5>
<div>host.access.log、host.error.logを設定。 9000番にきたらphp-fpmへつなげる。</div>
<pre> server {
listen 80;
listen [::]:80;
server_name ~^localhost$;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name ~^localhost$;
access_log /var/log/nginx/host.access.log;
error_log /var/log/nginx/host.error.log;
location / {
root /var/www/html/public;
index index.php index.html index.htm;
# リクエストされたファイルが存在しなければ、
# フロントコントローラーに内部リダイレクト
try_files $uri /index.php?$query_string;
}
error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
location ~ \.php$ {
root /var/www/html/public;
fastcgi_pass localhost$:9000;
fastcgi_index index.php;
# - 全てのリクエストをフロントコントローラーで実行
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include fastcgi_params;
}
}
</pre>
<h5>php-extends.ini</h5>
<div>phpエラーログとタイムゾーンの設定。</div>
<pre> [date]
date.timezone = "Asia/Tokyo"
[php]
log_errors = On
error_log = /var/log/php-fpm/php.log
</pre>
<h5>log.conf</h5>
<div>php-fpmのエラーログを設定。</div>
<pre> ; The access log file
; Default: not set
access.log = /var/log/php-fpm/access.log
; Error log file
; If it's set to "syslog", log is sent to syslogd instead of being written
; into a local file.
; Note: the default prefix is /usr/local/var
; Default Value: log/php-fpm.log
error_log = /var/log/php-fpm/error.log
</pre>
<h5>fluent.conf</h5>
<div>fluentdでログを受け取る。<br/> そのまま出力する設定。<br/> docckerコンテナのログのタグと、matchを変更することで受け取るログを判別することもできる。</div>
<pre> <source>
@type forward
port 24224
</source>
<match *>
@type stdout
</match>
</pre>
<h4>docker-compose.ymlの作成</h4>
<div>コンテナのloggingでdriverにfluentdを指定することで、fluentdにログを送ることができます。</div>
<pre>version: '3'
services:
nginx:
build: nginx:1.23.3
container_name: nginx
restart: always
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/local.conf:/etc/nginx/conf.d/default.conf
logging:
# ログ出力先にfluentdを指定
driver: "fluentd"
options:
# fluentdサーバー
fluentd-address: "localhost:24224"
# ログに付与するタグ
tag: "nginx"
fluentd-async-connect: "true"
depends_on:
- php-fpm
- fluent
php-fpm:
build: php:8.1.16-fpm
container_name: php-fpm
restart: always
volumes:
- ./php-fpm/php-extends.ini:/usr/local/etc/php/php-extends.ini
- ./php-fpm/log.conf:/usr/local/etc/php-fpm.d/log.conf
- ../:/var/www/html
logging:
# ログ出力先にfluentdを指定
driver: "fluentd"
options:
# fluentdサーバー
fluentd-address: "localhost:24224"
# ログに付与するタグ
tag: "php-fpm"
fluentd-async-connect: "true"
fluent:
image: "fluent/fluentd:v1.15-debian-1"
container_name: fluentd
ports:
- "24224:24224"
- "24224:24224/udp"
environment:
- 'FLUENTD_CONF=fluent.conf'
- 'TZ:Asia/Tokyo'
volumes:
- ./fluentd/config:/fluentd/etc/
</pre>
<h4>コンテナを起動する</h4>
<div>fluentdのログには、nginxとphp-fpmのログを取得する準備ができていることを確認できます。<br/> 起動したコンテナのログがfluentdで確認できるようになりました。</div>
<pre> 2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: init supervisor logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: parsing config file is succeeded path="/fluentd/etc/fluent.conf"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: gem 'fluentd' version '1.15.3'
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: using configuration file: <ROOT>
2023-09-25 12:16:25 <source>
2023-09-25 12:16:25 @type forward
2023-09-25 12:16:25 port 24224
2023-09-25 12:16:25 </source>
2023-09-25 12:16:25 <match nginx>
2023-09-25 12:16:25 @type stdout
2023-09-25 12:16:25 </match>
2023-09-25 12:16:25 <match php-fpm>
2023-09-25 12:16:25 @type stdout
2023-09-25 12:16:25 </match>
2023-09-25 12:16:25 </ROOT>
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: starting fluentd-1.15.3 pid=7 ruby="3.1.3"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: spawn command to main: cmdline=["/usr/local/bin/ruby", "-Eascii-8bit:ascii-8bit", "/usr/local/bundle/bin/fluentd", "--config", "/fluentd/etc/fluent.conf", "--plugin", "/fluentd/plugins", "--under-supervisor"]
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: init supervisor logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 init worker0 logger path=nil rotate_age=nil rotate_size=nil
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding match pattern="nginx" type="stdout"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding match pattern="php-fpm" type="stdout"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: adding source type="forward"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 starting fluentd worker pid=16 ppid=7 worker=0
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 listening port port=24224 bind="0.0.0.0"
2023-09-25 12:16:25 2023-09-25 03:16:25 +0000 [info]: #0 fluentd worker is now running worker=0
</pre>django admin画面に複数選択可能なカスタムリストフィルターを追加する2023-09-27T09:09:45+00:002024-03-28T10:01:06+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/django-admin%E7%94%BB%E9%9D%A2%E3%81%AB%E8%A4%87%E6%95%B0%E9%81%B8%E6%8A%9E%E5%8F%AF%E8%83%BD%E3%81%AA%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%AA%E3%82%B9%E3%83%88%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B/<div>django admin画面のフィルター機能に不満があるので、 複数選択可能なListFilterを作成します。</div>
<br/>
<h4>modelを準備する</h4>
<div>以下のようなmodelを準備します。<br/> 商品マスタ product<br/> 作家マスタ author<br/> 出版社マスタ publisher</div>
<pre>class Product(models.Model):
product_id = models.BigAutoField(
primary_key=True,
verbose_name='商品マスタID'
)
product_name = models.TextField(
verbose_name='商品名'
)
product_price = models.IntegerField(
verbose_name='商品価格',
)
author = models.ForeignKey(
Author,
on_delete=models.PROTECT
)
publisher = models.ForeignKey(
Publisher,
on_delete=models.PROTECT
)
class Meta:
managed = False
db_table = 'product'
verbose_name = '商品マスタ'
verbose_name_plural = '商品マスタ'
def __str__(self):
return self.product_name
class Publisher(models.Model):
publisher_id = models.BigAutoField(
primary_key=True,
verbose_name='出版社マスタID'
)
publisher_name = models.TextField(
verbose_name='出版社名'
)
class Meta:
managed = False
db_table = 'publisher'
verbose_name = '出版社マスタ'
verbose_name_plural = '出版社マスタ'
def __str__(self):
return self.publisher_name
class Author(models.Model):
author_id = models.BigAutoField(
primary_key=True,
verbose_name='作家マスタID'
)
author_name = models.TextField(
verbose_name='作家名'
)
class Meta:
managed = False
db_table = 'author'
verbose_name = '作家マスタ'
verbose_name_plural = '作家マスタ'
def __str__(self):
return self.author_name
</pre>
<br/>
<h4>商品マスタを管理画面で呼び出せるようにadmin.pyに以下のように記述します</h4>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>これで商品マスタ product が表示できるようになりました。</div>
<br/>
<h4>新たにコミックや小説などのカテゴリを記録するためのカテゴリマスタを作成します</h4>
<pre>class Category(models.Model):
category_id = models.BigAutoField(
primary_key=True,
verbose_name='カテゴリマスタID'
)
category_name = models.TextField(
verbose_name='カテゴリ名'
)
class Meta:
managed = False
db_table = 'category'
verbose_name = 'カテゴリマスタ'
verbose_name_plural = 'カテゴリマスタ'
def __str__(self):
return self.category_name
</pre>
<div>商品マスタ product にも外部キーでカテゴリマスタを設定します。</div>
<pre> category = models.ForeignKey(
Category,
on_delete=models.PROTECT
)
</pre>
<div>これでカテゴリも表示できるようになりました。<br/> admin.pyに追加すれが表示できるのですが、カテゴリは複数選択できるようにしたいとおもいました。<br/> そこで複数選択可能なカスタムリストフィルターを作成します。<br/>
<h4>複数選択可能なカスタムリストフィルター作成する</h4>
<div>admin.pyに ListFilter を継承した複数選択可能なカスタムリストフィルターを作成します。</div>
<pre> class MultiSelectListFilter(admin.ListFilter):
"""
複数選択可能なリストフィルター
admin の ListFilter を継承しています
"""
title = '' # 項目として表示される名称
model = '' # 使用するmodel名
parameter_name = '' # リクエストのパラメータ名
field_name = '' # 使用するmodelのフィールド名
def __init__(self, request, params, model, model_admin):
# ListFilter を継承
super().__init__(request, params, model, model_admin)
# 複数選択にしたいのでリクエスト値を in にします
self.lookup_kwarg = '%s__in' % self.parameter_name
# ここらへんは SimpleListFilter と同じ
if self.lookup_kwarg in params:
value = params.pop(self.lookup_kwarg)
self.used_parameters[self.lookup_kwarg] = value
lookup_choices = self.lookups(request, model_admin)
if lookup_choices is None:
lookup_choices = ()
self.lookup_choices = list(lookup_choices)
def has_output(self):
# SimpleListFilter と同じ
return len(self.lookup_choices) > 0
def value(self):
# SimpleListFilter と同じ
return self.used_parameters.get(self.lookup_kwarg)
def expected_parameters(self):
# SimpleListFilter と同じ
return [self.lookup_kwarg]
def lookups(self, request, model_admin):
ids = {}
if self.value():
# リクエスト値がある場合、選択済みの値を除外したtupleを返す
for v in self.model.objects.all():
current = self.value().split(',')
parameter_name = str(getattr(v, self.parameter_name))
if parameter_name in current:
current.remove(parameter_name)
else:
current.append(parameter_name)
str_ids = ','.join(list(current))
ids.update({str_ids: getattr(v, self.field_name)})
return tuple(ids.items())
else:
# デフォルトは全件表示。パラメータ名、フィールド名のtupleを返す
return self.model.objects.all().values_list(
self.parameter_name,
self.field_name
)
def choices(self, changelist):
# 全て を表示
yield {
'selected': self.value() is None,
'query_string': changelist.get_query_string(
remove=[self.parameter_name]
),
'display': _('All'),
}
# 選択済みの値はselectedで表示する。この判定のためにis_match関数を作成している
for lookup, title in self.lookup_choices:
yield {
'selected': self.is_match(lookup, self.value()),
'query_string': changelist.get_query_string(
{self.lookup_kwarg: lookup}
),
'display': title,
}
def queryset(self, request, queryset):
if self.value():
# 選択されたデータで絞り込んで表示
query = {
self.lookup_kwarg: self.value().split(','),
}
return queryset.filter(**query)
else:
# デフォルトの表示
return queryset.all()
@staticmethod
def is_match(lookup: str, value: str):
"""
選択済みの値とリクエストの値が一致しているか判定する
:param lookup: 選択済みの値
:param value: リクエストの値
:return:
"""
if not lookup:
return True
if not value:
return False
if all(v in lookup.split(',') for v in value.split(',')):
return False
else:
return True
</pre>
<div>admin.py に カテゴリマスタとカスタムリストフィルターでの表示を記載します。</div>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
class CategoryListFilter(MultiSelectListFilter):
"""
カテゴリマスタのカスタムフィルターの設定
"""
title = 'カテゴリ'
model = Category
parameter_name = 'category_id'
field_name = 'category_name'
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
'category', # カテゴリマスタの追加
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
CategoryListFilter, # カテゴリマスタのリストフィルターの追加
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>商品マスタの表示から「小説」「文庫」を選択</div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-2023.09.29-17_02_17.png"/></div>
<div>これで複数選択の絞り込みができるようになりました。</div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-2023.09.29-17_02_34.png"/></div>
<div>SimpleListFilter が便利なのですが、ちょっと痒いところに手が届かなかったので作成しました。</div>
</div>django admin画面に簡単にファイルの入力・出力を追加する2023-09-19T03:26:10+00:002024-03-28T11:39:15+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/django-admin%E7%94%BB%E9%9D%A2%E3%81%AB%E7%B0%A1%E5%8D%98%E3%81%AB%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E5%85%A5%E5%8A%9B%E5%87%BA%E5%8A%9B%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B/<div>django admin画面はmodelのデータ管理に非常に便利な機能です。<br/> csvなどのファイルでのデータの追加、出力ができるとさらに便利です。<br/> <code>django-import-export</code>プラグインを導入することでこの機能を簡単に導入することができます。</div>
<br/>
<h4>django-import-exportプラグインを追加する</h4>
<pre>pip install django-import-export
</pre>
<br/>
<h4>modelを準備する</h4>
<div>以下のようなmodelを準備します。<br/> 商品マスタ product<br/> 作家マスタ author<br/> 出版社マスタ publisher</div>
<pre>class Product(models.Model):
product_id = models.BigAutoField(
primary_key=True,
verbose_name='商品マスタID'
)
product_name = models.TextField(
verbose_name='商品名'
)
product_price = models.IntegerField(
verbose_name='商品価格',
)
author = models.ForeignKey(
Author,
on_delete=models.DO_NOTHING
)
publisher = models.ForeignKey(
Publisher,
on_delete=models.DO_NOTHING
)
class Meta:
managed = False
db_table = 'product'
verbose_name = '商品マスタ'
verbose_name_plural = '商品マスタ'
def __str__(self):
return self.product_name
class Publisher(models.Model):
publisher_id = models.BigAutoField(
primary_key=True,
verbose_name='出版社マスタID'
)
publisher_name = models.TextField(
verbose_name='出版社名'
)
class Meta:
managed = False
db_table = 'publisher'
verbose_name = '出版社マスタ'
verbose_name_plural = '出版社マスタ'
def __str__(self):
return self.publisher_name
class Author(models.Model):
author_id = models.BigAutoField(
primary_key=True,
verbose_name='作家マスタID'
)
author_name = models.TextField(
verbose_name='作家名'
)
class Meta:
managed = False
db_table = 'author'
verbose_name = '作家マスタ'
verbose_name_plural = '作家マスタ'
def __str__(self):
return self.author_name
</pre>
<br/>
<h4>商品マスタを管理画面で呼び出せるようにadmin.pyに以下のように記述します</h4>
<pre>@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
list_display = (
'product_id',
'product_name',
'product_price',
'author',
'publisher',
)
list_display_links = (
'product_name',
)
list_filter = (
'author',
'publisher',
)
search_fields = (
'product_name',
)
ordering = (
'product_id',
)
</pre>
<div>これで商品マスタ product が表示できるようになりました。</div>
<br/>
<h4>ファイルのインポート・エクスポート機能を追加する</h4>
<div>admin.pyにプラグインの読み込みを記述します</div>
<pre>from manga_server_django.admin.import_export import CommonImportExportSetting
</pre>
<div>ファイルのインポート・エクスポート機能を追加するためにadmin.pyにresourcesを追加します。<br/> 出力するmodelの項目名を指定します。<br/> 項目名の日本語名への変換や、取り込み時に外部キーで対応する項目名の指定などもできます。</div>
<pre>class ProductResource(resources.ModelResource):
# modelの項目名を指定。フィールド名・日本語表記の変換などもここで行う
product_id = Field(
attribute='product_id',
column_name='商品マスタID'
)
product_name = Field(
attribute='product_name',
column_name='商品名'
)
product_price = Field(
attribute='product_price',
column_name='商品価格'
)
# 外部キーのフィールドの場合はForeignKeyWidgetを記述する
publisher_name = Field(
attribute='publisher_name',
column_name='出版社名'
widget=ForeignKeyWidget(Publisher, 'publisher_name'),
)
author_name = Field(
attribute='author_name',
column_name='作者名'
widget=ForeignKeyWidget(Author, 'author_name'),
)
class Meta:
model = Product
# 出力設定
# ヘッダーに出力する文字列
headers = (
'商品マスタID',
'商品名',
'商品価格',
'出版社名',
'作者名',
)
# 出力するフィールド指定
fields = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 出力するフィールドの順番指定
export_order = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 入力設定
# 入力の主キーとなる項目の指定
import_id_fields = ('product_id',)
# 入力するフィールドの指定
import_order = (
'product_id',
'product_name',
'product_price',
'publisher_name',
'author_name',
)
# 更新のない行はスキップする
skip_unchanged = True
# 結果表示はスキップしない
report_skipped = False
</pre>
<div>ProductAdminに<code>ImportExportMixin</code>を追加。<br/> <code>resource_class</code>に先ほど作成した<code>ProductResource</code>を指定します。</div>
<pre>@admin.register(Product)
class ProductAdmin(ImportExportMixin, admin.ModelAdmin):
# 省略
resource_class = ProductResource
# 入力・出力するフォーマット。下記ではcsvとExcelファイルにしています。他にもtsv、YAML、XMLなども指定できます。
formats = [
base_formats.CSV,
base_formats.XLS,
base_formats.XLSX,
]
# 出力する文字コード指定
to_encoding = 'CP932'
# 入力する文字コード指定
from_encoding = 'CP932'
</pre>
<div>これで管理画面から商品マスタのインポート・エクスポートができるようになりました。</div>
<br/>
<h5>admin画面 上部の表示</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_14_35.png"/></div>
<h5>エクスポート画面</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_27_21.png"/></div>
<h5>インポート画面</h5>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"><img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/screenshot-torico.manga-server.com-2023.09.19-13_30_30.png"/></div>
<br/>
<div>ちょっとしたプラグインの追加でdjango admin画面で大量のデータをより使いやすくできるので非常に便利です。</div>nginx+FPMの環境のタイムアウトの解決法の備忘録2023-03-17T02:15:01+00:002024-03-29T02:07:00+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/nginxfpm%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%AE%E3%82%BF%E3%82%A4%E3%83%A0%E3%82%A2%E3%82%A6%E3%83%88%E3%81%AE%E8%A7%A3%E6%B1%BA%E6%B3%95%E3%81%AE%E5%82%99%E5%BF%98%E9%8C%B2/<div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"> <img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/php-logo.svg.png" width="256px"/></div>
</div>
前回、記事を書いた<a href="https://admin.torico-corp.com/blog/php%E3%82%92nginxfpm%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%A7%E5%8B%95%E4%BD%9C%E3%81%95%E3%81%9B%E3%82%8B/" title="PHPをnginx+FPMの環境で動作させる">PHPをnginx+FPMの環境</a>で、実際にwebアプリを作成。<br/>ページが重くなる場合にタイムアウトが発生、その原因が複数の理由のため解決に時間がかかったのでその備忘録。<br/><br/>さきに原因を書くと<br/>
<ol>
<li>phpの実行時間によるタイムアウト</li>
<li>ブラウザとnginxのタイムアウト</li>
<li>nginxとphp-fpmのタイムアウト</li>
</ol>
と3つの箇所でタイムアウトが発生していた。<br/><br/> まずは最初に表示された
<pre>504 Gateway Timeout</pre>
を解消する。 <br/>phpのタイムアウト時間<code>max_execution_time</code>を設定して確認。<br/>まだ504エラーが表示されるので、つぎにnginxの<code>send_timeout</code>と<code>keepalive_timeout</code>を設定。<br/>この設定の追加で504エラーは表示されなくなったが、別のエラーがnginxから表示されるようになった。
<pre>An error occurrerd. Sorry, the page you are looking for is currently unavailable. Please try again later.</pre>
<img src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/an_error_occurrerd.png" width="500"/><br/>このエラーメッセージがnginxとphp-fpmのタイムアウトだと気づくのに時間がかかった。<br/>これの対応はnginxの設定に<code>fastcgi_connect_timeout</code>、<code>fastcgi_read_timeout</code>、<code>fastcgi_read_timeout</code>を追加することで解消できた。<br/> <br/> 最終的に<strong>nginx.conf</strong>と<strong>php.ini</strong>に下記の設定を追加しています。 <br/><strong>nginx.conf</strong>
<pre>http {
send_timeout 300; # クライアントへの応答のタイムアウト時間
keepalive_timeout 300; # クライアントとの接続をキープする時間
fastcgi_connect_timeout 300; # nginxとphp-fpmの接続を確立するためのタイムアウト時間
fastcgi_send_timeout 300; # nginxからphp-fpmへのリクエスト送信のタイムアウト時間
fastcgi_read_timeout 300; # php-fpmからの応答のタイムアウト時間
}</pre>
<strong>php.ini</strong>
<pre>max_execution_time = 600 # phpのスクリプトの実行時間</pre>
<br/> webサーバーとアプリケーション・サーバーを分けた場合、その間のタイムアウトも気にしなければいけなかった。PHPをnginx+FPMの環境で動作させる2023-03-07T00:49:45+00:002024-03-29T02:04:52+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/php%E3%82%92nginxfpm%E3%81%AE%E7%92%B0%E5%A2%83%E3%81%A7%E5%8B%95%E4%BD%9C%E3%81%95%E3%81%9B%E3%82%8B/<div>
<div style="display: block; margin-left: auto; margin-right: auto;" width="512px"> <img alt="" src="https://d1qjlssvz4u32r.cloudfront.net/media/uploads/site-6/kudou/php-logo.svg.png" width="256px"/></div>
</div>
古いPHPのWebアプリの改修を行うことになりました。<br/> まずはメンテナンス性の確保のためにオレオレフレームワークからLaravelに載せ替える。<br/> また、1台サーバーを使用して動かしていたが、あまり大きなアプリではないので、kubernetesで動作させることにします。<br/> そして合わせて将来的な技術投資としてWebサーバーをApacheからnginx + FastCGI Process Manager(FPM)を試してみることにしました。<br/> その際のdokcerとkubernetesの設定の備忘録になります。<br/> <br/>
<h4>nginxのconfファイルの作成</h4>
まずはLaravelとphp-fpm用にしたnginxのconfファイルを作成。<br/> <strong>default.conf</strong><br/>
<pre>server {
listen 80;
listen [::]:80;
server_name example.com;
access_log /var/log/nginx/host.access.log;
error_log /var/log/nginx/host.error.log;
location / {
root /usr/share/nginx/html;
index index.php index.html index.htm;
# リクエストされたファイルが存在しなければ、Laravelのフロントコントローラーに内部リダイレクトさせる
try_files $uri /index.php?$query_string;
}
# 400番台のエラーページの設定
error_page 404 /404.html;
# 500番台のエラーページの設定
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
# Apacheの設定なので今回はコメントアウトのまま
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# FastCGIの設定
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
# Laravelのルートディレクトリ
root /var/www/html/public;
# nginxからphp-fpmに受け渡すIPアドレスとポート番号の設定
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
# 全てのリクエストをLaravelのフロントコントローラーで実行させる
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
include fastcgi_params;
}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
</pre>
ここでの注意点はnginxからphp-fpmに受け渡すIPアドレスとポート番号の設定<br/> ローカル環境で動作させる場合はdokcerのphp-fpmのコンテナを指定してください。<br/>
<pre>fastcgi_pass php-fpm:9000;
</pre>
kubernetesで動作させる場合は1pod内でnginxのコンテナとphp-fpmのコンテナを動作させるため<code>127.0.0.1:9000</code>で受け取ることができます<br/> <br/>
<h4>nginxのDockerfileの作成</h4>
Docker Hubから最新のものを使用。<br/> <strong>Dockerfile</strong><br/>
<pre>FROM nginx:1.23.3
COPY docker/nginx/default.conf /etc/nginx/conf.d/default.conf
EXPOSE 80 443
STOPSIGNAL SIGQUIT
CMD ["nginx", "-g", "daemon off;"]
</pre>
<br/>
<h4>php-fpmのDockerfileの作成</h4>
こちらもDocker Hubから最新のものを使用。<br/> 注意点はnginxがアクセスできるようにLaravelのプロジェクトを配置しているディレクトリの所有ユーザーを変更すること。<br/> アクセスしてくるのはnginxなので所有ユーザーを<code>www-data</code>に変更しておかないと404エラーになります。<br/> <strong>Dockerfile</strong><br/>
<pre>FROM php:8.1.16-fpm
#PHPの拡張機能のインストール
RUN apt update \
&& apt install -y libonig-dev libxml2-dev libcurl4-openssl-dev libssl-dev libzip-dev \
&& docker-php-ext-install pdo pdo_mysql mysqli simplexml curl phar zip ftp \
&& pecl install xdebug redis \
&& docker-php-ext-enable xdebug redis
# nginxがアクセスできるように所有ユーザーを変更
COPY project /var/www/html
RUN chown -R www-data:www-data /var/www/html
EXPOSE 9000
ENTRYPOINT ["docker-php-entrypoint"]
CMD ["php-fpm"]
</pre>
<br/>
<h4>kubernetesの設定の作成</h4>
kubernetesに反映させるdeployment、service、ingressを作成します。<br/> <strong>deployment.yaml</strong><br/> 1pod内で動作させるためcontainersにnginxとphp-fpmのコンテナを記載します。<br/>
<pre>apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
labels:
app: example
spec:
replicas: 1
selector:
matchLabels:
app: example
template:
metadata:
labels:
app: example
spec:
containers:
- image: php-fpm:latest
name: example-php-fpm
ports:
- containerPort: 9000
protocol: TCP
- image: nginx:latest
name: example-nginx
ports:
- containerPort: 80
protocol: TCP
</pre>
<strong>service.yaml</strong><br/> 外からのアクセスはnginxが受けるのでポートは80番。<br/>
<pre>kind: Service
apiVersion: v1
metadata:
name: example-service
spec:
type: NodePort
selector:
app: example
ports:
- port: 80
targetPort: 80
protocol: TCP
</pre>
<strong>ingress.yaml</strong><br/>
<pre>apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
</pre>
<br/> kubernetesに反映を実行。 これで動作できました。mysqlのセキュリティ更新で新たなユーザー権限が必要になっていた2023-02-13T03:00:47+00:002024-03-28T19:01:52+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/%E4%B9%85%E3%80%85%E3%81%ABmysqldump%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E9%9A%9B%E3%81%AE%E8%A9%B1/<div></div>
<h3>久々にmysqldumpを使用した際の話</h3>
<p>とある検証のために本番環境の最新のデータを検証環境のデータベースにいれる必要があり、久々にmysqldumpを実行。</p>
<p>まずはテーブル名を指定してdumpファイルを作成する。</p>
<pre>mysqldump -uユーザー名 -pパスワード -hホスト スキーマ名 テーブル名 > /tmp/dump.sql </pre>
<p>実行。</p>
<pre> Access denied; you need (at least one of) the PROCESS privilege(s) for this operation </pre>
<p>アクセス拒否、PROCESS権限が必要です。</p>
<p>以前なら問題なく実行できていたユーザーなのに、必要な権限が増えている?</p>
<p>ということで調べてみると、リリースノートのセキュリティ注釈に記載があり</p>
<a href="https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html">https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html</a><br/>
<pre>Security Notes Incompatible Change: Access to the INFORMATION_SCHEMA.FILES table now requires the PROCESS privilege. This change affects users of the mysqldump command, which accesses tablespace information in the FILES table, and thus now requires the PROCESS privilege as well. Users who do not need to dump tablespace information can work around this requirement by invoking mysqldump with the --no-tablespaces option. (Bug #30350829) The linked OpenSSL library for MySQL Server has been updated to version 1.1.1g. Issues fixed in the new OpenSSL version are described at https://www.openssl.org/news/cl111.txt and https://www.openssl.org/news/vulnerabilities.html. (Bug #31296697)</pre>
<p>PROCESS権限が必要になっていました。</p>
<p>ユーザーに権原を付与する、または</p>
<code> --no-tablespaces </code>
<p>オプションをつけることで回避できるそうです。</p>
<p>今回はオプションで対応しました。</p>
<p>改めて実行。</p>
<pre>mysqldump -uユーザー名 -pパスワード -hホスト --no-tablespaces スキーマ名 テーブル名 > /tmp/dump.sql</pre>
<p>今度は問題なくダンプ終了</p>
<pre>mysql -uユーザー名 -pパスワード -hホスト スキーマ名 < /tmp/dump.sql</pre>
<p>ロードする。</p>
<pre>Access denied; you need (at least one of) the SUPER privilege(s) for this operation
</pre>
<p>さらにアクセス拒否が表示される。SUPER権限が必要です?</p>
<p>これについては</p>
<code> SET @@GLOBAL.GTID_PURGED </code>
<p>が原因でした</p>
<a href="https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_set-gtid-purged">https://dev.mysql.com/doc/refman/5.7/en/mysqldump.html#option_mysqldump_set-gtid-purged</a><br/>
<pre>This option enables control over global transaction ID (GTID) information written to the dump file, by indicating whether to add a SET @@GLOBAL.gtid_purged statement to the output. This option may also cause a statement to be written to the output that disables binary logging while the dump file is being reloaded.
</pre>
<p>グローバルトランザクションIDについては変更したくないので、これもオプションで対応。</p>
<code> --set-gtid-purged=OFF </code>
<p>を追記して、再度ダンプし直します。</p>
<pre> mysqldump -uユーザー名 -pパスワード -hホスト --no-tablespaces --set-gtid-purged=OFF スキーマ名 テーブル名 > /tmp/dump.sql </pre>
<p>そしてロードする。</p>
<pre>mysql -uユーザー名 -pパスワード -hホスト スキーマ名 < /tmp/dump.sql</pre>
<p>問題なくできました。これで最新のデータで<span><span>検証を行うことが</span></span>できそうです。</p>照合順序が違うカラムの外部結合2022-09-26T07:10:17+00:002024-03-28T17:09:18+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/%E7%85%A7%E5%90%88%E9%A0%86%E5%BA%8F%E3%81%8C%E9%81%95%E3%81%86%E3%82%AB%E3%83%A9%E3%83%A0%E3%81%AE%E5%A4%96%E9%83%A8%E7%B5%90%E5%90%88/<div></div>
<h3>増築を繰り返しているMysqlデータベースを使用しているとたまにある問題の解決方法</h3>
<p>作成されたタイミングの違うテーブルの照合順序があっていないことがあります。<br/> 古いテーブルのカラムの照合順序が<strong>utf8_unicode_ci</strong>で、<br/> 最近作られたテーブルのカラムの照合順序が<strong>utf8_general_ci</strong><br/> 必要データがこのふたつのテーブルにあるため外部結合しなければならない自体になった。</p>
<p>普通にLEFT JOINしようとすると</p>
<pre>SELECT t1.*
FROM t1
LEFT JOIN t2
ON t1.code = t2.code
</pre>
<p>照合順序が違うカラム同士では外部結合できないとエラーが発生します。</p>
<code>Illegal mix of collations (utf8_unicode_ci,IMPLICIT) and (utf8_general_ci,IMPLICIT) for operation '=' </code>
<p>これの解決のためにカラムの設定を変えてしまうのは影響範囲が大きいのでできれば避けたい。<br/> そんな場合にはCOLLATE句を使用することで解決できます。</p>
<pre>SELECT t1.*
FROM t1
LEFT JOIN t2
ON t1.code COLLATE utf8_general_ci = t2.code
</pre>
<p>これで問題なく外部結合して結果を取得することができました。</p>
<p>mysqlのドキュメントにはWHERE句、ORDER句、GROUP句などでの使い方は書いてありましたが、外部結合での使い方は書いてなかったので備忘録として残しておきます。</p>
<p>MySQL 5.6 リファレンスマニュアル <a href="https://dev.mysql.com/doc/refman/5.6/ja/charset-collate.html" target="_blank">10.1.7.2 SQL ステートメントでの COLLATE の使用</a></p>10年に一度巡る雑誌コードの話2021-09-24T02:17:55+00:002024-03-28T20:21:58+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/10%E5%B9%B4%E3%81%AB%E4%B8%80%E5%BA%A6%E5%B7%A1%E3%82%8B%E9%9B%91%E8%AA%8C%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E8%A9%B1/<p></p>
<p>ISBNコード(書籍JANコード)とともに本に付番されている雑誌コードについて解説します。<br/> 雑誌コードからJANコードを作成しなければならない時に役に立つ知識です。</p>
<h3>雑誌コードとは</h3>
<p>雑誌扱いの本に付番されている<code>5桁+4桁</code>、または<code>5桁+2桁</code>のコードです。<br/> この5桁に意味がずっしりと詰まっています。<br/> また、バーコードはISBNコード(書籍JANコード)と一般商品JANコードのものがあります。<br/> 雑誌コードの始まる<strong>1桁目</strong>で雑誌コードの種類がきまります。</p>
<h4>1. 1桁目が<code>0</code>、<code>1</code>は月刊誌(隔月刊や季刊の雑誌も含む)</h4>
<p>基本、末尾は奇数。別冊や増刊号は末尾の数に<strong>+1</strong>して偶数になる。<br/> <strong>本に印刷されるバーコードは一般商品JANコード</strong></p>
<p>例)雑誌コード5桁+「-」+月2桁+年の下2桁</p>
<pre>月刊TORICO 8月号
01234-0821、または省略して01234-08
月刊TORICO 8月増刊号
01235-0821、または省略して01235-08
</pre>
<h4>2. 1桁目が<code>2</code>、<code>3</code>は週刊誌(隔週刊や月2回発売の雑誌も含む)</h4>
<p>末尾は発売した週で1〜5。<br/> 別冊や増刊号は末尾に6以上の数が振られる。<br/> <strong>本に印刷されるバーコードは一般商品JANコード</strong></p>
<p>例)雑誌コード5桁+「-」+月2桁+年の下2桁</p>
<pre>週刊TORICO 8/6号(8月1週目)
21231-0821、または21231-8/6
週刊TORICO 8/13号(8月2週目)
21232-0821、または21232-8/13
週刊TORICO 8/20号(8月3週目)
21233-0821、または21233-8/20
週刊TORICO 8/20増刊号
21236-0821、または21236-8/20
</pre>
<h4>3. 1桁目が<code>4</code>、<code>5</code>は漫画のコミックス</h4>
<p>雑誌コード5桁は固定。<br/> だいたいレーベルごとになっている(〇〇コミック)<br/> 月年はなく雑誌コードに対して発売された巻数の通巻数2桁。<br/> <strong>本に印刷されるバーコードは書籍JANコード</strong></p>
<p>例)雑誌コード5桁+「-」+通巻数2桁</p>
<pre>TORICOコミックス 1巻
41234-01
TORICOコミックス 2巻
41234-02
</pre>
<h4>4. 1桁目が<code>6</code>はムック</h4>
<p>雑誌コード5桁は固定。<br/> だいたいレーベルごとになっている(〇〇ムック)<br/> 月年はなく雑誌コードに対して発売された巻数の通巻数2桁。<br/> <strong>本に印刷されるバーコードは書籍JANコード</strong></p>
<p>例)雑誌コード5桁+「-」+通巻数2桁</p>
<pre>TORICOムック よくわかるTORICO
61234-01
TORICOムック TORICO大全
61234-02
</pre>
<h4>5. 珍しいコード</h4>
<p>1桁目が<code>7</code>で始まる、オーディオ・ビジュアル商品<br/> 1桁目が<code>8</code>で始まる、直販誌<br/> 1桁目が<code>9</code>で始まる、PB商品</p>
<h3>雑誌コードから一般商品JANコードの作成方法</h3>
<p>月刊誌、週刊誌にはISBNコード(書籍JANコード)がありません。<br/> 雑誌コードのみ付番されています。<br/> ではバーコードは何でできているか?というと雑誌コードから一般商品JANコードを作成することができます。</p>
<p>例)01234-0821の場合<br/> 一般商品JANコードは<code>4910012340819</code>となります。<br/> 内訳としては<br/> <code>491</code>=>定期刊行物コードのプレフィックス。固定<br/> <code>0</code>=>予備。雑誌コードが満数になった場合使うかもしれない<br/> <code>01234</code>=>雑誌コード5桁<br/> <code>08</code>=>月号2桁<br/> <code>1</code>=>年の下1桁<br/> <code>9</code>=>JANのチェックデジット。形式はモジュラス10、ウエイト1、3<br/> これをphpで書くと下記の様になります</p>
<pre>/**
* 雑誌コードをJANコードに変換する
* @param string $code 雑誌コード、5桁-4桁
* @return string JANコード
*/
function magazineCode2Jan($code)
{
// 雑誌コードと月号で分ける
$exp_code = explode('-', $code);
$magazine_code = $exp_code[0];
// 年の下1桁
$year = substr($exp_code[1], 3, 1);
$month = substr($exp_code[1], 0, 2);
$prefix = 491;
$spare = 0;
$jan12 = $prefix . $spare . $magazine_code . $month . $year;
// チェックデジット計算
$chkdgt = getCheckDigit13($jan12);
$jan13 = $jan12 . $chkdgt;
return $jan13;
}
</pre>
<p>※チェックデジットの計算は<a href="https://tech.torico-corp.com/blog/%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E7%9F%A5%E3%82%89%E3%81%AA%E3%81%84isbn%E3%81%AE%E8%A9%B1/%0A">エンジニアの知らないISBNの話</a>のコード例にある「ISBN13桁のチェックディジット」を使用しています。<br/> ここでタイトル回収になるのですが、<strong>年の下1桁</strong>を使用するため雑誌コードは<strong>10年に一度同じコード</strong>になります。<br/> データベースにカラムを作る際は<strong>ユニーク制約をしない</strong>、あるいは<strong>絶版になった雑誌を定期的に削除する</strong>ような仕組みが必要になります。</p>エンジニアの知らないISBNの話2021-09-15T08:07:27+00:002024-03-28T19:32:39+00:00工藤淳https://tech.torico-corp.com/blog/author/kudou/https://tech.torico-corp.com/blog/%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%8B%E3%82%A2%E3%81%AE%E7%9F%A5%E3%82%89%E3%81%AA%E3%81%84isbn%E3%81%AE%E8%A9%B1/<p></p>
<p>本には商品判別のためのバーコードがついています。<br/> いわゆるISBNコード(JANコード)です。<br/> ECショップでは本を取り扱うために本の書誌を記録するテーブルを作成しますが、<strong>その際にint型でいいだろう? ユニーク制約でいいか?</strong>とおもってはいけないません。</p>
<p>先に結論を書いてしまうと</p>
<ul>
<li>文字が含まれる可能性がある</li>
<li>先頭に0が来る可能性がある</li>
<li>ユニークではない</li>
</ul>
<h3>char型にして、ユニーク制約をつけないほうがよい理由</h3>
<h4>1. 文字「X」が含まれる可能性がある</h4>
<p>ISBNコードには10桁と13桁があります。<br/> 10桁ISBNコードは9桁+チェックデジット1桁で構成されます。<br/> 古い体系ですが、一部で使うことがあり、チェックデジットに文字「X」がはいる可能性があります。<br/> これはチェックデジットが<code>モジュラス11、ウェイト10-2</code>という方法で算出されるためです。<br/> 計算方法をphpで書くと</p>
<pre>/**
* ISBN10桁のチェックディジットを返す
*/
function getCheckDigit10($jan9)
{
$sum = 0;
$cnt = 0;
for ($i = 10; ; $i--) {
if ($cnt > 10) {
break;
}
$sum += substr($jan9, $cnt, 1) * $i;
$cnt++;
}
$remainder = $sum % 11;
if ($remainder == 0) {
return 0;
}
$check_digit = 11 - $remainder;
if ($check_digit == 10) {
$check_digit = 'X';
}
return $check_digit;
}
</pre>
<h4>2. 先頭に0がつく可能性がある</h4>
<p>ISBNコードと書きましたが、実際にバーコードになっているのはJANコードです。<br/> そしてJANコードは先頭が「0」になる可能性があります。<br/> 本を売るショップとして取り扱いそうなJANコードは3種類。</p>
<ol>
<li>一般商品JANコード<br/> 45,または49で始まる12桁+チェックデジット1桁の13桁JANコード。<br/> 45と49は日本に割り振られた国コードなので固定。<br/> 雑誌やグッズなどに使われています。</li>
<li>書籍JANコード<br/> 9784で始まる12桁+チェックデジット1桁の13桁JANコード。<br/> 978は世界共通の書籍コード<br/> 4は日本に割り振られた国コードなので固定。<br/> いわゆる13桁ISBNコード。<br/> コミックや書籍などに使われています。<br/> バーコードは2段になっており、上段は書籍JANコード、分類・価格のコード。</li>
<li>インストアコード<br/> 02、04、20〜29で始まる12桁+チェックデジット1桁の13桁JANコード。<br/> ポイントカード、会員証やレジ袋などの管理・販売用に使われる、小売店が独自につくって使用してよいJANコード。<br/> 20〜29始まりのJANコードを使用すればいい話だが、02、04も準備しておくに越したことはない。</li>
</ol>
<p>JANコードのチェックデジットは<code>モジュラス10、ウエイト3-1</code>という方法で算出されるため0〜9の数字になります。</p>
<pre>/**
* ISBN13桁のチェックディジットを返す
*/
function getCheckDigit13($jan12)
{
$sum_odd = 0;
$sum_even = 0;
for ($i = 11; $i >= 0; $i--) {
if ($i % 2) {
$sum_odd += substr($jan12, $i, 1);
} else {
$sum_even += substr($jan12, $i, 1);
}
}
$sum = $sum_odd * 3 + $sum_even;
$division = $sum % 10;
return (10 - $division) % 10;
}</pre>
<h4>3. 同じJANコードが使われる</h4>
<p>JANコードは同じコードが存在するのでユニークではありません。</p>
<ol>
<li>書籍JANコードの場合<br/><strong> 「今現在販売中の本」</strong>と限定すれば書籍JANコードはユニークになります。<br/> ただし、本が絶版になったあと、そのJANコードは別の本に付番されます。<br/> そのため、今までに発売したすべての本のJANコードだと同じJANコードをもつ本が複数存在します。</li>
<li>雑誌の一般商品JANコードの場合<br/> 雑誌に付番されている雑誌コードから一般商品JANコードが作成されます。<br/> その際に西暦の下1桁を使用しているため、10年ごとに同じコードになります。</li>
</ol>
<p>以上がISBNコード(JANコード)をint型、ユニーク制約にしては行けない理由でした。<br/> 10桁と13桁のどちらも記録できるようにしてあると便利かもしれません。</p>