Atsushi2022の日記

データエンジニアリングに関連する記事を投稿してます

読書メモ「プロのためのLinuxシステム構築・運用技術」

概要

「プロのためのLinuxシステム構築・運用技術」を読んで、大事そうなところをメモしておく。

結構前に投稿しようとしていたけど放置してた。途中までしかメモ作成できてないけど、このまま放置してしまいそうなのでとりあえず投稿しておく。

第1章 Linuxサーバーの基礎

ただし、これは、あくまで一般のLinuxユーザーから見たときの話です。Linuxサーバーを構築・運用するシステム管理者は、さまざまなデバイスが搭載された物理的なハードウェアにLinuxを導入して、「架空のコンピューター」を描き出せるよう、適切に構成することが自身の仕事です。そのため、「架空のコンピューター」の裏にあるハードウェアコンポーネントを理解して、それがLinuxによって、どのように「架空のコンピューター」へと変換されているのかを知ることが重要になります。Linuxを理解することは、この「変換の仕組み」を理解することと同じといっても過言ではありません。


それぞれのサーバー機器に対して、サポート対象となるLinuxディストリビューションと、そのバージョンが決まっています。業務用サーバーを使用する場合は、このようなサポート情報にも注意する必要があります。


サーバー用途でLinuxをインストールする際は、ディスクのパーティション構成にも注意する必要があります。Linuxでは、インストール先のディスクを複数のパーティションに分割して使用しますが、特別な理由がない限り、作成するパーティションは、表1.4に示したもの程度にしておきます*9。古いUnixサーバーでは、/var、/usrなどのディレクトリーを個別のパーティションに分けることもありましたが、Linuxではこのような必要はありません。

パーティション 説明
ブートパーティション 「/boot」ディレクト
ルートパーティション 「/ディレクトリ」
swap パーティション swap領域
kdumpパーティション kdumpのダンプファイル出力先
ホームパーティション 「/home」ディレクト
データ用パーティション アプリケーションのデータ領域

パーティションのサイズは、次のように考えます。まず、ブートパーティションは、カーネル本体、初期RAMディスクに加えて、GRUB2に関連したファイルを格納します。RHEL7での推奨は500MBですが、通常はこの容量で十分でしょう。ルートパーティションは、導入予定のRPMパッケージとアプリケーションソフトウェアの導入・実行に必要な容量を確保します。RHEL7に同梱のRPMパッケージについては、すべて導入する場合でも10GBあれば十分です。アプリケーションのデータ領域に外部ストレージを使用する場合(表1.4の「データ用パーティション」を使用しない場合)は、ほかのパーティションを割り当てた残り容量をすべてルートパーティションにしてもかまわないでしょう。

サーバーのパフォーマンスの観点では、swap領域はなるべく使用しないほうが望ましいので、業務用サーバーの場合は、アプリケーションプログラムが使用するメモリー容量を事前に見積もって、必要な量の物理メモリーを搭載しておくのが普通です。この場合、理屈の上では、swap領域は不要となります。ただし、ディスクキャッシュとしてメモリーが大量に消費される場合がありますので、安全のために2GB~4GB程度のswap領域を用意しておくのがよいでしょう*

kdumpパーティションは、「6.2カーネルダンプの取得」で説明する、カーネルダンプのダンプファイル出力先として使用します。デフォルトでは、ルートファイルシステムディレクトリー/var/crash以下に保存されるので、kdumpパーティションは必ずしも作成する必要はありません。ただし、この場合は、ルートファイルシステムの容量が不足すると、ダンプファイルの保存に失敗する可能性もあります。ダンプファイルを保存するためのkdumpパーティションを用意することで、保存先の容量を確保できます。kdumpパーティションには、物理メモリーの2倍程度の容量を確保しておくとよいでしょう。ホームパーティションは、/home以下にある、一般ユーザーのホームディレクトリーとして使用するためのパーティションです。ホームパーティションを用意するかどうかは、サーバーの利用用途に応じて決定します。複数のユーザーがログインして、ホームディレクトリーに個人ファイルを保存するような場合は、ホームパーティションを用意することで、個人ファイルが大量に保存されたためにルートファイルシステムの容量が不足するなどの問題を防止できます。一方、一般的なサーバー用途で、一般ユーザーの個人ファイルを保存しない環境であれば、ホームパーティションは作成せずに、ホームディレクトリーはルートファイルシステムに作成しても問題ないでしょう。ホームパーティションを用意する場合は、ホームディレクトリーとして使用したいディスク容量に合わせて、サイズを決定してください。

最後に、データ用パーティションは、アプリケーションのデータ保存領域のように、運用中にデータ量が継続的に増加していくディレクトリーとして使用します。このパーティションのサイズについては、保存するデータ量の上限をあらかじめ見積もっておき、運用中に容量が不足しないように決定します。データ量の上限が事前に決められない場合は、データ用パーティションを使用するのではなく、データ保存専用のディスクを別に用意するほうがよいでしょう。この際、「3.2LVMの構成・管理」で説明する、論理ボリュームマネージャー(LVM:LogicalVolumeManager)を用いるなどして、データ領域のサイズを後から拡張できるようにしておきます。なお、RHEL7のGUIインストーラーを用いる場合、デフォルトでは、ディスクパーティションではなく、LVMを利用した構成となります。この部分は、パーティション構成に変更してインストールを進めることをお勧めします。LVMを用いた構成の場合、「2.2バックアップ」で説明するシステムバックアップの手順が複雑になったり、万一、ファイルシステムに障害が発生した際に、復旧の難易度が高くなるなどのデメリットがあるためです。LVMを利用すれば、後からファイルシステムのサイズを拡張できるというメリットはありますが、運用中に増加していくデータをシステム領域に配置すること自体が間違っているともいえます。データ量が一定の領域と、データ量が増加する領域をきちんと分けて管理することが、ディスク管理の基本となります。


RedHatEnterpriseLinuxに含まれるソフトウェアは、すべて、RPMパッケージで提供されます。それぞれのパッケージにはバージョン番号が付けられており、新しく発見された不具合の修正やセキュリティ問題への対応が行われると、新しいバージョンのパッケージが提供されます。インターネットに接続したサーバーであれば、RedHatNetworkに登録することで、インターネット上のリポジトリーから、最新のRPMパッケージを入手できるようになります。RedHatNetworkへの登録は、subscription-managerコマンドで行います*


サーバーベンダーが提供するデバイスドライバーの中には、利用可能なカーネルのバージョンが決められている場合があります。特定バージョンのカーネルを使用する際は、バージョン番号を指定してアップデートを行います。まず、次のコマンドを実行すると、リポジトリーに保存されているすべてのバージョンのパッケージが表示されます。


続いて、ログイン用の個人ユーザーを作成します。サーバーにログインして作業を行う際は、いきなりrootユーザーでログインするのではなく、個人ユーザーでログインした後に、必要な場合だけrootユーザーに切り替えるようにします。これは、サーバーにログインして作業した人間を明確にするためです。業務用サーバーでは、複数の人間がサーバーにログインして作業することがあるため、サーバーのログイン履歴から、「いつ誰がログインして作業したのか」を後から確認できるようにしておきます。また、rootユーザーで作業を行う権限を与えるユーザーは、wheelグループに追加しておきます。これにより、このユーザーは、rootユーザーのパスワードを知らなくても、sudoコマンドでrootユーザーに切り替えられるようになります。複数の人間でrootユーザーのパスワードを共有する運用は、パスワード変更時の連絡が煩雑になり、パスワードが漏洩する危険性も高くなるので避けたほうがよいでしょう。


また、ログインとsudoコマンド実行の履歴は、ログファイル/var/log/secureに記録されます。これを見ることにより、どのユーザーがいつログインして、rootユーザーに切り替えたかを確認できます。


インストール直後に実施しておくべき最低限のセキュリティ設定として、rootユーザーのログイン禁止とファイアウォールの構成があります。まず、先ほど説明したように、Linuxにログインして管理作業を行う際は、個人ユーザーでログインした後に、必要な場合だけrootユーザーに切り替えます。これを徹底するために、SSHデーモンの設定で、rootユーザーで直接にログインすることをシステム的に禁止しておきます。具体的には、設定ファイル/etc/ssh/sshd_configを開いて、図1.13の部分を変更します。


インストール直後のデフォルト状態では、外部からのアクセスは、ICMPパケットの受信、および、SSH接続(TCP22番ポート)のみが許可されています。しかしながら、デフォルトのままで安心するのではなく、具体的な設定内容を把握しておくことが大切です。RHEL7では、ファイアウォール機能を利用するにあたり、新機能の「firewalldサービス」と、以前からある「iptablesサービス」のどちらかが選択できます*13。デフォルトでは、firewalldサービスが用いられています


iptablesによって、外部からのパケット受信が拒否されると、図1.15のようなメッセージがシステムログ/var/log/messagesに記録されます。送信元IPアドレス(SRC)、送信元ポート番号(SPT)、宛先IPアドレス(DST)、宛先ポート番号(DPT)などの情報が記録されており、どのような端末からどのポートにアクセスがあったのかがわかります。

第2章サーバー運用の基礎

また、監視対象サーバー上で稼働するシステム監視ツールを「監視エージェント」といいます。これは、特定のシステム管理製品に付随するプログラムの場合や、単独で提供される簡易的なツールの場合があります。手作りのシェルスクリプトも立派な監視エージェントです。監視エージェントは、ログファイルに出力される内容の監視(ログファイル監視)、Linux上のプロセスに対する稼働状況の監視(プロセス監視)、そして、システムリソースの使用状況の監視(リソース監視)などを行います。この後で説明するように、システム管理プロセッサーから情報を収集することもできます。


監視エージェントの例として、ログファイルの監視に特化したスクリプト「logmon」[2]を紹介します。これは、設定ファイルで指定したログファイルへの書き込みを監視して、事前に定義した文字列を発見すると、それに対応したコマンドを実行します。このコマンドによって、サーバー管理者に通知メールを送付したり、外部のシステム管理サーバーにメッセージを送ることが可能になります。

設定ファイル/etc/logmon/logmon.confに監視の設定を記述します。


リソース使用量を中心としたシステム稼働情報を収集するツールにsysstatがあります。これは、Linuxでは古くから使用されているツールで、RHEL7には標準パッケージとして含まれています。次のコマンドでsysstatのRPMパッケージを導入すると、設定ファイル/etc/cron.d/sysstatにcronジョブのエントリーが追加されて、データの収集が開始されます。

sysstatパッケージには、iostatコマンドなど、システム稼働情報を収集するための標準コマンドが含まれていますので、新しくサーバーを構築したら、必ず導入しておくことをお勧めします。


Linuxでは、標準で提供されるrsyncコマンドを利用すると、ファイル単位でのデータバックアップを手軽に実施できます。cronジョブを利用して、定期的なバックアップを実施することも可能です。

rsyncコマンドは、リモートのサーバーにネットワーク経由でファイルをコピーすることもできます。

これを実行する際は、リモートのサーバーにもrsyncRPMパッケージが導入されている必要があります。また、リモートサーバーへのファイル転送には、内部的にSSH接続が用いられます。


psacctは、Linux上で実行された、すべてのコマンドやプロセスを記録します。具体的には、プロセスの終了時に、実行ユーザーや実行時刻などの情報をバイナリーの履歴ファイル/var/account/pacctに記録します。lastcommコマンドを使用すると、記録された情報をもとに、実行プロセスの履歴が確認できます。


PAM(PluggableAuthenticationModule)は、Linux上で稼働するソフトウェアに対して、ユーザー認証に関連する機能を提供するプラグインモジュールです。PAMを用いて、複数のソフトウェアに共通のユーザー認証の仕組みを提供することで、ユーザー認証の仕組みをシステム管理者が一元的に管理できます。たとえば、PAMの設定を変えることで、システムログイン時の認証方法をshadowファイルによるローカル認証からLDAP認証に変更したり、パスワード変更時のパスワードポリシー(最小文字数の指定など)を指定することが可能になります。

それぞれのソフトウェア(プログラム)が使用するPAMモジュールは、設定ファイル/etc/pam.d/<プログラム名>に記載します。たとえば、SSHデーモンの場合は、/etc/pam.d/sshdになります。また、対応する設定ファイルを持たないプログラムに対しては、/etc/pam.d/othersの設定が適用されます。


構成文書にどこまで詳細な情報を記載するかは、システムの重要度や運用ポリシーにも依存しますが、Linuxサーバーの管理者としては、最低限、次のような資料を整備しておきます。・サーバーハードウェアの物理構成とシステムBIOSファームウェアのバージョン・ネットワーク機器や外部ストレージ装置との物理的な接続図・ディスク装置の論理構成(RAIDの構成など)・OSの基本設定情報(RedHatEnterpriseLinuxであれば、sosreportコマンドで一括収集される情報)・ネットワーク構成情報(ホストネーム、IPアドレスなど)・OSに追加導入したツールや意図的に設定変更している箇所などの情報

第3章Linuxのストレージ管理

ストレージエリアネットワークは、サーバーとストレージ装置の間に、物理的な接続とは独立した、論理的な接続を実現する技術です。SAN(StorageAreaNetwork)とも呼ばれます。これにより、高性能な大型のストレージ装置を複数のLinuxサーバーから共有して使用することが可能になります。一般に、SAN接続に対応したストレージ装置を「SANストレージ」と呼びます。


多くのSANストレージは、LUNの物理コピー機能と論理コピー機能を持ちます。物理コピーは、既存のLUNの内容を同じサイズのLUNにそのままコピーします。物理コピーの実施中は、サーバー側ではコピー元のLUN上のファイルシステムはアンマウントしておきます。マウントした状態でコピーをすると、ファイルシステム内のデータの整合性が保証されません。LUNの容量に応じて、コピー処理が完了するまでの時間は長くなり、この間、サーバーからはLUNが使えない点に注意が必要です。論理コピーは、既存のLUNに対して、見かけ上は物理コピーと同等のLUNを作成します。ただし、実際のコピーは行いません。コピー元、もしくは、コピー先のLUNのどちらかに書き込みが行われたタイミングで、実際のコピー処理が行われます。このとき、書き込みによって発生する差分の保存領域(差分領域)を事前に用意する方式と、必要なデータブロックを動的に割り当てる方式の2種類の方式があります。動的な割り当て方式については、「3.3.1dm-thinの動作原理」で詳しく解説するので、ここでは差分領域を用いた仕組みを説明します。この方式の場合、既存のLUNから論理コピーを作成すると、図3.6のように、差分領域が確保されます。この後、どちらかのLUNに書き込みが発生すると、書き込み対象のブロックが差分領域にコピーされた後に実際の書き込みが行われます。図3.6では、コピー元のLUNに書き込みを行った場合を記載していますが、コピー先のLUNに書き込みを行った場合は、差分領域のブロックに対して書き込みが行われます。そして、コピー先のLUNからデータを読み込む際は、差分領域のデータを確認して、差分がない場合は、コピー元のLUNのデータを読み取ります。差分がある場合は、差分領域のデータを読み取ります。


差分領域を用いた仕組みの注意点として、コピー元とコピー先の差分が大きくなって、差分領域が不足すると、コピー先のLUNが使用できなくなるという点があります。そのため、コピー先の領域は、データバックアップの中間領域として使用するのが一般的です。


これは、LUN①から直接に物理コピーでLUN③を作成する場合と何が違うのでしょうか? この方法の場合、論理コピーでLUN②を作成した時点で、最初のLUN①はアプリケーションからの使用を再開することが可能です。物理コピーの実施中、長時間にわたってアプリケーションを停止する必要がなくなります。あるいは、図3.7のように、論理コピーしたLUNをバックアップサーバーにマッピングして、テープ装置にデータを書き出す方法もあります。この場合も、データバックアップに伴うアプリケーションの停止時間は、論理コピーが完了するまでの数秒間で済みます。


データ領域に使用するディスクは、LinuxのLVM(LogicalVolumeManager/論理ボリュームマネージャー)を用いて管理すると、複数の物理ディスクを1つにまとめて大容量のファイルシステムを作成したり、ファイルシステムのサイズを後から拡張できるなどの利点があります。

LinuxのLVMでは、物理ボリューム(PV)、ボリュームグループ(VG)、論理ボリューム(LV)の3つの概念を用いてディスクを管理します。まず、Linuxから認識している個々のデバイス(/dev/sdbなど)を「物理ボリューム」と呼びます。そして、複数の物理ボリュームをまとめたグループとして、「ボリュームグループ」を作成します。最後に、ボリュームグループの中に論理的なデバイスとなる「論理ボリューム」を作成していきます。

LVMを使用しない環境では、ディスク全体を複数のパーティションに分割して使用しますが、LVMの環境では、1つの論理ボリュームが1つのパーティションに対応すると考えられます(


準備として、LVMの利用に必要となるlvm2のRPMパッケージを導入して、サーバーを再起動しておきます。

次のコマンドは、/dev/sdb、/dev/sdcの2個のLUNから、ボリュームグループvg_data01を構成して、論理ボリュームlv_data01を作成する例になります。

pvcreateコマンドでは、/dev/sdbと/dev/sdcをLVMで管理する物理ボリュームとして登録しています。vgcreateコマンドでは、物理ボリューム/dev/sdbと/dev/sdcからなるボリュームグループvg_data01を作成しています。最後のlvcreateコマンドは、ボリュームグループvg_data01の中に5GBの論理ボリュームlv_data01を作成します。-i

作成された物理ボリューム、ボリュームグループ、論理ボリュームは、それぞれ、pvs、vgslvsコマンドで確認します。

あるいは、pvdisplay、vgdisplay、lvdisplayの各コマンドを使用すると、より詳細な情報が表示されます。pvcreateコマンドでLVMの管理対象となった物理ボリューム(LUN)には、UUIDと呼ばれる固有のID番号が割り当てられて、LUNの先頭部分のメタデータ領域に、UUIDに加えて、ボリュームグループなどのLVMの構成情報が記録されます。物理ボリュームのUUIDは、先ほどのpvdisplayコマンドで確認できます。続いて、論理ボリュームlv_data01をXFSファイルシステムでフォーマットして、ディレクトリー/dataにマウントします。

なお、システム起動時に自動でマウントするように/etc/fstabにエントリーを追加する場合は、デバイスファイル名/dev/vg_data01/lv_data01を用いるほか

lvextendコマンドでは、既存の論理ボリューム/dev/vg_data01/lv_data01に、2GBの容量を追加しています。この段階では、論理ボリューム内のファイルシステムのサイズは、まだ拡張されていません。次のxfs_growfsコマンドで、ファイルシステムのサイズを論理ボリュームのサイズに合わせて拡張しています。xfs_growfsコマンドは、ファイルシステムをマウントした状態で、マウントポイントを指定して実行する点に注意してください。ボリュームグループに含まれる物理ボリュームの容量が不足する場合は、新しい物理ボリュームを追加できます。次は、物理ボリューム/dev/sddを作成して、既存のボリュームグループvg_data01に追加する例です。


ボリュームグループのいくつかの状態の遷移を表します。ボリュームグループには、大きく「Imported」と「Exported」の2つの状態があります。通常の使用中は、Importedの状態のままで変化することはありません。

ボリュームグループの状態がAvailableのときは、中に含まれる論理ボリュームのファイルシステムをマウントして使用することができます。Linuxが起動した直後は、ボリュームグループの状態はNotavailableですが、起動処理の中でvgchangeコマンドが実行されて、自動的にAvailableになります。Notavailableに変更する際は、ボリュームグループに含まれるすべての論理ボリュームのファイルシステムをアンマウントしておきます。論理ボリューム名やボリュームグループ名の変更を行う際は、Notavailableの状態で行います。

Exportedの状態への変更は、ボリュームグループを構成する物理ディスクをサーバーから取り外して、ほかのサーバーに付け替える際に使用します。Notavailable状態のボリュームグループに対して、次のコマンドで、ExportedとImportedの状態を変更します。

Exportedの状態のボリュームグループは、LVMの管理対象外と認識されるため、対応する物理ディスクを安全にサーバーから取り外して、ほかのサーバーに移動できます。

第4章Linuxのネットワーク管理

ます。以前のバージョンでは、このような情報はarpコマンドおよびifconfigコマンドで確認していましたが、RHEL7では、これらのコマンドを提供するnet-toolsのRPMパッケージがデフォルトではインストールされなくなっており、代替としてipコマンドを使用するようになっています。


ます。RHEL7では、NetworkManagerサービスによってネットワークの設定が管理されており、nmcliコマンドを用いて設定を行います。


以前のLinuxでは、NICのデバイス名は伝統的に「eth0」「eth1」という名称が用いられていました。この場合、Linuxサーバーを起動すると、LinuxカーネルNICを認識した順番でデバイス番号が振られていきます。したがって、どのNICにどのデバイス名が割り当てられるかは、サーバーの構成によって変わります。一方、RHEL7では、「PredictableNetworkInterfaceNames(予測可能なインターフェース名)」と呼ばれる仕組みが導入されて、NICの物理的な場所に応じて、固定的なデバイス名が割り当てられるようになりました。具体例としては、「eno1」「enp0s25」「wlp2s0」などのデバイス名になります。最近のサーバーハードウェアは、システムBIOS(あるいはUEFI)がサーバーに搭載されたNICの論理番号を提供するようになっており、これはシステム構成によって変わることはありません。そこで、Linuxの側でもこの論理番号を用いて、NICのデバイス名を割り当てます。


NetworkManagerによる、ネットワークの設定/管理の考え方を説明します。NetworkManagerでは、それぞれのNICを表す「デバイス」と、それに適用する設定内容を示す「接続」を別々に定義するという特徴があります。「デバイス」に「接続」をひも付けることで、設定内容がデバイスに適用されます(図4.16)。複数の接続を事前に定義しておいて、デバイスに対するひも付けを変更することにより、あるデバイスのネットワーク設定を切り替えるといった使い方も可能です。これは、ノートPCのLinuxデスクトップ環境など、ネットワーク接続が頻繁に変わる環境での利用を想定した機能になります。また、このようなネットワーク設定の変更は、サーバーの稼働中に実施できます。設定変更を反映するために、サーバーを再起動する必要はありません。


インストーラーでネットワークの設定を行った場合は、このようにデバイス名と同じ名前の接続が用意されます。ただし、デバイス名と接続名は必ずしも一致する必要はありません。


このとき、接続の設定を記載したファイルが/etc/sysconfig/network-scripts/ifcfg-eno2-conとして用意されます。以前のLinuxでは、デバイスごとに設定ファイルが用意されていましたが、ここでは、デバイスではなく接続に対して設定ファイルが用意される点に注意が必要です。


ipv4.dnsipv4.dns-searchで指定した内容は、名前解決用の設定ファイル/etc/resolv.confにおける、nameserverとsearchのエントリーに反映されます。


IPアドレスについても複数設定することが可能です。1つのNICに複数のIPアドレスを割り当てる際、以前は「IPエイリアス」と呼ばれる仕組みを用いていました。NetworkManagerでは、セカンダリIPアドレスと呼ばれる機能で同じことを実現します。設定方法は簡単で、次のようにカンマ区切りで複数のIPアドレスを指定するだけです。


「4.1.1IPネットワークの基礎」では、IPネットワーク上でパケットが転送される仕組みを説明しましたが、IPネットワークそのものには、パケットが宛先のアドレスまで届いたことを保証する仕組みはありません。たとえば、転送経路上のルーターが停止している場合、パケットはそこで失われます。また、サーバー上のアプリケーションと通信する場合、宛先のIPアドレスを指定するだけでは、通信先のサーバーは決まるものの、そのパケットがどのアプリケーションに向けて送信されたのかはわかりません。このような問題を解決するのが「ソケット通信」です。ソケット通信は、IPネットワークを通して、アプリケーションどうしで通信するための仕組みで、「TCP」と「UDP」の2種類のプロトコルがあります。特に、ソケット通信では、サーバーアプリケーションとクライアントアプリケーションが区別されます。サーバーアプリケーションのプロセスは、ネットワークからの接続を常時受け付けており、これに対してクライアントアプリケーションが接続しにいく形になります。ソケット通信の仕組みは、基本的にはアプリケーションプログラマーが理解するべき内容で、Linuxサーバーの管理者が意識することはそれほど多くはありません。しかしながら、ネットワークに起因するアプリケーションの問題が発生した際は、Linux上でソケット通信の状態を確認する必要があります。この後で説明する、TCP接続に関するカーネルパラメーターの確認が必要な場合もあるでしょう。ここでは、Linuxサーバーの管理者として理解しておくべき、ソケット通信の基本事項を説明します。


このとき、「サーバーのIPアドレスとポート番号、および、クライアントのIPアドレスとポート番号」のペアーで、お互いの通信相手が特定されます。一般に、「IPアドレスとポート番号」の組を「ソケット」と呼びます。TCPUDPの違いは、通信を開始する前に「セッション」を確立するかどうかです。簡単にいうと、UDPで通信するクライアントアプリケーションは、サーバーアプリケーションが稼働しているかどうかを確認せずに、単純にIPアドレスとポート番号を指定してパケットを送信します。アプリケーションからの応答がなかった場合、ネットワークの問題でパケットが到達しなかったのか、アプリケーションの問題で応答がなかったのかは区別できません。応答がない場合にパケットを再送するなどの処理は、それぞれのアプリケーションで実装する必要があります。一方、TCPで通信する場合は、この後で説明する方法で、事前にサーバーアプリケーションとクライアントアプリケーションの間でセッションを確立します。TCPセッションが確立すると、各送信パケットには、「シーケンスナンバー」と呼ばれる番号が割り当てられます。パケットの受信側は、何番の番号までパケットを受信したかという、受信確認の「ACKパケット」を送信します。これにより、パケットが確実に受信されたことが保証されます。また、送信側において一定時間「ACKパケット」を受け取らなかった場合は、同じパケットを再送します。このようなTCPセッションの仕組みは、Linuxが提供するTCPレイヤーの機能で行われるので、TCPで通信するアプリケーションは、パケットの再送処理などを気にする必要がなくなります。


TCP通信におけるソケットは、電源用のテーブルタップをイメージするとよいでしょう(図4.30)。サーバーのポート番号ごとにテーブルタップが用意されており、複数のクライアントと接続することができます。接続待ちのソケットにクライアントが接続すると、新たに接続待ちのソケットが追加されます。サーバーアプリケーションは、1つのポート番号で複数の接続を行いますが、クライアントアプリケーションは、接続ごとに自分自身のポート番号を用意します。


設定ファイル/etc/servicesには、アプリケーション名とポート番号の対応が記載されています。

$ cat /etc/services
# Network services, Internet style
#
# Updated from https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml .
#
# New ports will be added on request if they have been officially assigned
# by IANA and used in the real-world or are needed by a debian package.
# If you need a huge list of used numbers please install the nmap package.

tcpmux          1/tcp                           # TCP port service multiplexer
echo            7/tcp
echo            7/udp
discard         9/tcp           sink null
discard         9/udp           sink null
systat          11/tcp          users
daytime         13/tcp
daytime         13/udp
netstat         15/tcp
qotd            17/tcp          quote
chargen         19/tcp          ttytst source
chargen         19/udp          ttytst source
ftp-data        20/tcp
ftp             21/tcp
fsp             21/udp          fspd
ssh             22/tcp                          # SSH Remote Login Protocol
telnet          23/tcp
smtp            25/tcp          mail

インターネットに公開したWebサーバーなど、多数のネットワーク接続が発生するシステムでは、サーバー上で同時に利用できるソケット数が上限に達して、それ以上クライアントの接続が受け付けられなくなることがあります。このような上限値を決めるパラメーターには、プロセスごとに設定される「ファイルディスクリプタ数」の上限と、サーバー全体で設定される「ファイルオブジェクト数」の上限があります。ここでは、これらのパラメーターの変更方法を説明します。ファイルディスクリプタ数の上限Linuxには、個々のプロセスが同時にオープンできるファイル数をulimitによって制限する機能があります。これは正確には、個々のプロセスが使用できるファイルディスクリプタ数の上限になります。ファイルディスクリプタは、Linuxカーネルが管理する、各プロセスがオープン中のファイルを識別するラベルです。Linuxでは、仮想ファイルシステムの機能により、物理ディスク上のファイル以外に、標準入出力やTCPソケットなどのリソースも仮想的にファイルとして扱われます。つまり、これらのリソースを使用する際もファイルディスクリプタが必要となります。したがって、HTTPデーモンなどネットワーク通信を多用するプロセスでは、ulimitによるファイルディスクリプタ数の制限により、同時接続可能なクライアント数が制限される場合があります。多数のネットワーク接続が予想されるシステムでは、ulimitによるファイルディスクリプタ数の制限を増やしておく必要があります。設定可能な最大値は、カーネルパラメーターfs.nr_openで決められており、デフォルト値は1048576です。このカーネルパラメーターの設定可能な最大値は2147483584です。


ユーザーのログインシェルに対して設定する場合は、設定ファイル/etc/security/limits.confに図4.32の内容を記載します。soft(ソフトリミット)が実際の制限値で、hard(ハードリミット)は、個々のユーザーがulimitコマンドで変更する際の変更可能な上限値です。

ユーザーのログインシェルに対して設定する場合は、設定ファイル/etc/security/limits.confに図4.32の内容を記載します。soft(ソフトリミット)が実際の制限値で、hard(ハードリミット)は、個々のユーザーがulimitコマンドで変更する際の変更可能な上限値です。

ユーザーのログインシェル以外に、systemdが管理するサービスとして、サーバー起動時に自動起動するプロセスに設定する場合は、systemdの設定ファイルに設定値を記載します。該当サービスの設定ファイルの[service]セクションにおいて、「LimitNOFILE=65536」のように記載します。

$ cat /usr/lib/systemd/system/ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service

各 プロセス が 使用 中 の ファイル ディスクリプタ を 確認 する には、 プロセス ID を < pid > として 次 の コマンド を 実行 し ます。

$ ps
    PID TTY          TIME CMD
    697 pts/0    00:00:00 bash
   2632 pts/0    00:00:00 ps
$ ls -l /proc/697/fd
total 0
lrwx------ 1 bluen bluen 64 Aug 11 17:06 0 -> /dev/pts/0
lrwx------ 1 bluen bluen 64 Aug 11 17:06 1 -> /dev/pts/0
lrwx------ 1 bluen bluen 64 Aug 11 17:06 2 -> /dev/pts/0
lrwx------ 1 bluen bluen 64 Aug 11 17:06 255 -> /dev/pts/0

前述のように、Linuxでは仮想ファイルシステムの機能により、さまざまなシステムリソースを仮想的なファイルとして取り扱います。Linuxカーネルは、内部的にファイルオブジェクトと呼ばれる変数を用意して、このような仮想ファイルの情報を格納します。このとき、ユーザープロセスが無尽蔵にファイルをオープンしたり非常に多くのネットワーク接続を行うと、カーネルが使用するファイルオブジェクトが増加していき、最終的にシステムメモリーの不足を招きます。ファイルディスクリプタ数の制限には、このようなトラブルを防ぐ働きがありますが、Linuxでは個々のプロセスのファイルディスクリプタ数以外に、システム全体で使用可能なファイルオブジェクト数にも制限を設けています。ファイルオブジェクト数の上限は、カーネルパラメーターfs.file-maxで設定します。実質的には、任意の大きさの値を設定することができますが、システムメモリーの不足を保護するためのパラメーターなので、極端に大きな値を設定することは避けてください。デフォルト値は、サーバーに搭載された物理メモリーの容量に応じて自動的に決まるようになっています。

第5章プロセス管理

$ ps aux
$ ps aux | column -t
USER      PID   %CPU  %MEM  VSZ      RSS     TTY    STAT  START  TIME  COMMAND
root      1     0.0   0.3   167564   12836   ?      Ss    13:15  0:01  /sbin/init
root      2     0.0   0.0   2324     1192    ?      Sl    13:15  0:00  /init
root      5     0.0   0.0   2352     80      ?      Sl    13:15  0:00  plan9                                                   --control-socket                                            6                                    --log-level      4                          --server-fd                                                                        7              --pipe-fd  9                  --log-truncate
root      42    0.0   0.4   48384    15972   ?      S<s   13:15  0:00  /lib/systemd/systemd-journald
root      68    0.0   0.1   22580    6188    ?      Ss    13:15  0:00  /lib/systemd/systemd-udevd
root      79    0.0   0.0   4496     176     ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/bare_5.snap                            /snap/bare/5                         -o               ro,nodev,allow_other,suid
root      82    0.0   0.0   4852     1908    ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/core22_766.snap                        /snap/core22/766                     -o               ro,nodev,allow_other,suid
root      87    0.0   0.0   4628     180     ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/gtk-common-themes_1535.snap            /snap/gtk-common-themes/1535         -o               ro,nodev,allow_other,suid
root      91    0.0   0.0   4496     156     ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/nmap_3031.snap                         /snap/nmap/3031                      -o               ro,nodev,allow_other,suid
root      99    0.0   0.0   4628     200     ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/nmap_3077.snap                         /snap/nmap/3077                      -o               ro,nodev,allow_other,suid
root      102   0.0   0.0   4496     204     ?      Ss    13:15  0:00  snapfuse                                                /var/lib/snapd/snaps/snapd_19122.snap                       /snap/snapd/19122                    -o               ro,nodev,allow_other,suid
root      106   0.0   0.0   4784     1848    ?      Ss    13:15  0:03  snapfuse                                                /var/lib/snapd/snaps/snapd_19457.snap                       /snap/snapd/19457                    -o               ro,nodev,allow_other,suid
root      111   0.0   0.0   4752     1852    ?      Ss    13:15  0:01  snapfuse                                                /var/lib/snapd/snaps/ubuntu-desktop-installer_1104.snap     /snap/ubuntu-desktop-installer/1104  -o               ro,nodev,allow_other,suid
systemd+  127   0.0   0.3   25548    12612   ?      Ss    13:15  0:00  /lib/systemd/systemd-resolved
root      151   0.0   0.0   4324     2792    ?      Ss    13:15  0:00  /usr/sbin/cron                                          -f                                                          -P
message+  153   0.0   0.1   9108     5024    ?      Ss    13:15  0:00  @dbus-daemon                                            --system                                                    --address=systemd:                   --nofork         --nopidfile                --systemd-activation                                                               --syslog-only
root      168   0.0   0.4   30156    18488   ?      Ss    13:15  0:00  /usr/bin/python3                                        /usr/bin/networkd-dispatcher                                --run-startup-triggers
root      173   0.0   0.2   236644   9140    ?      Ssl   13:15  0:00  /usr/libexec/polkitd                                    --no-debug
syslog    175   0.0   0.1   222400   5116    ?      Ssl   13:15  0:00  /usr/sbin/rsyslogd                                      -n                                                          -iNONE
root      183   0.0   1.5   1614204  61840   ?      Ssl   13:15  0:06  /usr/lib/snapd/snapd
root      190   0.0   0.1   15348    7312    ?      Ss    13:15  0:00  /lib/systemd/systemd-logind
root      195   0.0   0.1   15116    6856    ?      Ss    13:15  0:00  /lib/systemd/systemd-machined
root      196   0.0   0.3   392672   14520   ?      Ssl   13:15  0:00  /usr/libexec/udisks2/udisksd
root      197   0.0   0.1   16492    5676    ?      Ss    13:15  0:00  /sbin/wpa_supplicant                                    -u                                                          -s                                   -O               /run/wpa_supplicant
root      253   0.0   1.0   124844   39564   ?      Ssl   13:15  0:00  /usr/bin/python3                                        /usr/sbin/firewalld                                         --nofork                             --nopid
root      321   0.0   0.4   255428   17368   ?      Ssl   13:15  0:00  /usr/sbin/NetworkManager                                --no-daemon
root      335   0.0   0.6   1547712  27212   ?      Ssl   13:15  0:00  /usr/sbin/libvirtd
root      348   0.0   0.5   107248   21200   ?      Ssl   13:15  0:00  /usr/bin/python3                                        /usr/share/unattended-upgrades/unattended-upgrade-shutdown  --wait-for-signal
root      355   0.0   0.0   3236     1064    hvc0   Ss+   13:15  0:00  /sbin/agetty                                            -o                                                          -p                                   --               \u                         --noclear                                                                          --keep-baud    console    115200,38400,9600  vt220
root      369   0.0   0.2   15444    8592    ?      Ss    13:15  0:00  sshd:                                                   /usr/sbin/sshd                                              -D                                   [listener]       0                          of                                                                                 10-100         startups
root      374   0.0   0.0   3192     1088    tty1   Ss+   13:15  0:00  /sbin/agetty                                            -o                                                          -p                                   --               \u                         --noclear                                                                          tty1           linux
root      398   0.0   0.5   197336   20648   ?      Ss    13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
mysql     426   0.0   2.6   1608300  102652  ?      Ssl   13:15  0:03  /usr/sbin/mariadbd
www-data  428   0.0   0.2   197848   10548   ?      S     13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
www-data  429   0.0   0.2   197848   10548   ?      S     13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
www-data  430   0.0   0.2   197848   10548   ?      S     13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
www-data  431   0.0   0.2   197848   10548   ?      S     13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
www-data  432   0.0   0.2   197848   10548   ?      S     13:15  0:00  /usr/sbin/apache2                                       -k                                                          start
root      695   0.0   0.0   2332     108     ?      Ss    13:15  0:00  /init
root      696   0.0   0.0   2348     116     ?      S     13:15  0:00  /init
hogehoge     697   0.0   0.1   6312     5448    pts/0  Ss    13:15  0:00  -bash
root      698   0.0   0.1   7552     4984    pts/1  Ss    13:15  0:00  /bin/login                                              -f
bluhogehoge     en     739   0.0   0.2   17188    9684    ?      Ss    13:15  0:00  /lib/systemd/systemd                                    --user
bluhogehoge     en     754   0.0   0.0   169508   3864    ?      S     13:15  0:00  (sd-pam)
hogehoge     768   0.0   0.1   34252    6280    ?      Ssl   13:15  0:00  /usr/bin/pipewire
hogehoge     769   0.0   0.1   18276    6504    ?      Ssl   13:15  0:00  /usr/bin/pipewire-media-session
hogehoge     770   0.0   0.1   6124     4884    pts/1  S+    13:15  0:00  -bash
rtkit     772   0.0   0.0   154000   1532    ?      SNsl  13:15  0:00  /usr/libexec/rtkit-daemon
hogehoge     783   0.0   0.1   8316     4100    ?      Ss    13:15  0:00  /usr/bin/dbus-daemon                                    --session                                                   --address=systemd:                   --nofork         --nopidfile                --systemd-activation                                                               --syslog-only
root      959   0.0   0.0   4852     1640    ?      Ss    13:20  0:00  snapfuse                                                /var/lib/snapd/snaps/core22_858.snap                        /snap/core22/858                     -o               ro,nodev,allow_other,suid
root      1105  0.0   0.0   4984     1660    ?      Ss    13:21  0:01  snapfuse                                                /var/lib/snapd/snaps/ubuntu-desktop-installer_1200.snap     /snap/ubuntu-desktop-installer/1200  -o               ro,nodev,allow_other,suid
root      1192  0.0   0.0   4776     3284    ?      Ss    13:21  0:00  /bin/bash                                               /snap/ubuntu-desktop-installer/1200/bin/subiquity-server
root      1222  0.0   2.3   1179408  90456   ?      Sl    13:21  0:12  /snap/ubuntu-desktop-installer/1200/usr/bin/python3.10  -m                                                          subiquity.cmd.server                 --use-os-prober  --storage-version=2        --postinst-hooks-dir=/snap/ubuntu-desktop-installer/1200/etc/subiquity/postinst.d
root      1255  0.1   0.9   43184    37720   ?      S     13:21  0:17  python3                                                 /snap/ubuntu-desktop-installer/1200/usr/bin/cloud-init      status                               --wait
root      2010  0.0   0.5   293004   20316   ?      Ssl   13:42  0:00  /usr/libexec/packagekitd
hogehoge     2686  0.0   0.0   7484     3256    pts/0  R+    17:11  0:00  ps                                                      aux
hogehoge     2687  0.0   0.0   3320     1068    pts/0  S+    17:11  0:00  column                                                  -t







親プロセスからフォークで起動したプロセスは、図5.3のさまざまな状態を遷移していきます。図5.3の「STAT」の記号は、図5.2の「STAT」の列の1文字目(R、D、S、T、Z)に対応します。

「R(実行可能状態/実行状態)」は、プロセスにCPU時間が割り当てられて、実行されている状態です。厳密には、複数のプロセスが順番にCPUを使用するために、実際にCPUを使用している瞬間(実行状態)と、CPUの割り当て待ちの瞬間(実行可能状態)があります。psコマンドでは、これらをまとめて「R」と表示します。「D(割り込み不能な待機状態)」は、プロセスがディスクアクセスの命令を発行した後に、ディスクI/Oが完了するのを待っている状態です。この状態のプロセスが多数存在する場合は、システム全体で、ディスクI/Oの負荷が高い可能性があります。また、この状態のプロセスは、シグナルによる強制終了ができません。デバイスドライバーの障害でI/Oが完了せず、しかもエラーにもならないような場合に、「D」状態のプロセスが停止できずに問題になることがあります。このような場合は、基本的にはシステムを再起動するしかありません。「S(割り込み可能な待機状態)」は、プログラムが自発的にスリープしている状態です。Webサーバーのプロセスが、クライアントからのアクセスが来るまで、何もせずに待っているような状態です。「T(停止状態)」は、STOPシグナルを受けて、一時停止している状態です。CONTシグナルを送ると、プロセスは実行状態に戻ります。最後の「Z(ゾンビ状態)」は、プログラムの実行が終了した状態です。実行が完了したプロセスは、親プロセスにCHLDシグナルを送信して、この状態で停止します。その後、CHLDシグナルを受けた親プロセスが子プロセスの完了確認処理を実施すると、このプロセスは完全に消滅します。親プロセスが異常な状態になり、CHLDシグナルを正しく受信できない場合、ゾンビ状態のプロセスはそのまま残り続けます。ゾンビ状態のプロセスは、psコマンドで、プロセス名の後ろにと表示されます。


「R(実行可能状態/実行状態)」のプロセスには、順番にCPU時間が割り当てられていきます。このとき、各プロセスに割り当てるCPU時間を管理するために、内部的な優先度が設定されます。プロセスの優先度の値が小さいほど、実行の優先順位が高く、CPUの割り当て時間が長くなります。プロセスの優先度は、プロセスの実行状況に応じてカーネルが自動的に調整するので、ユーザーレベルでの変更はできません。その代わりに、プロセスの「Niceレベル」を設定することで、間接的に優先度の変化を調整します。Niceレベルの範囲は、-20~19(-20が優先度最高、19が優先度最低)で、デフォルト値は0です*2。プロセスの起動時に、niceコマンドでNiceレベルを指定します。あるいは、reniceコマンドで稼働中のプロセスのNiceレベルを変更できます。


Linuxでは、ulimitと呼ばれる仕組みによって、各プロセスが利用できるさまざまなリソースに一定の制限がかけられています。


systemdでは、図5.5と同様の起動処理を「Unit」によって実施します。SysVinitにおいてシェルスクリプトの中で実施されていたそれぞれの処理は、すべて個別のUnitとして定義されており、Unitごとに専用のプログラムが処理を行います。Unitにはいくつかの種類があり、systemdが自動的に生成するものと、システム管理者が明示的に定義するものがあります。主なUnitの種類は表5.3のとおりで、Unit名の拡張子部分でUnitの種類が区別されます。システム管理者が主に設定・管理するのは、「service」タイプのUnitです。これは、sshdSSHデーモン)やhttpd(HTTPデーモン)などのサービスを起動するためのUnitです。


Unitの定義ファイルは、/usr/lib/systemd/system(デフォルトの設定内容)と/etc/systemd/system(デフォルトから変更した内容)の2カ所のディレクトリーに保存されています。「sshd.service」「httpd.service」など、Unit名がそのまま設定ファイル名になっており、両方のディレクトリーに設定ファイルがある場合は、/etc以下のほうが優先されます。デフォルトの設定内容を変更する場合は、/usr/lib以下の設定ファイルを/etc以下のほうにコピーした上で変更を加えます。デフォルトの設定ファイルはそのまま残っているため、コピーしたファイルを削除すれば、簡単にデフォルトの設定に戻すことができます。既存の設定に対して、一部の設定のみを追加/変更する場合は、/etc/systemd/systemの下に<Unit名>.dというサブディレクトリーを作成して、そこに任意のファイル名で設定ファイルを保存する方法があります。


まず、それぞれのUnitの設定ファイルにはUnit間の依存関係が設定されており、あるUnitを実行する際に、その前提となるUnitが指定されます。systemdは、はじめに「default.target」という名前のUnitの設定ファイルを見て、その前提となるUnitを順にたどることにより、システム全体として実行するべきUnitの一覧表を作成します。

systemdがはじめに確認する設定ファイルdefault.targetは、実際には、graphical.targetやmulti-user.targetなど、ほかの設定ファイルへのシンボリックリンクになっており、このリンク先を変更することは、起動時のランレベルを変更することに相当します。

先ほど、Unitの設定ファイル内部で依存関係が設定されると説明しましたが、そのほかに、<Unit名>.dというサブディレクトリーにほかのUnitの設定ファイルへのシンボリックリンクを作成することでも依存関係が設定されます。サービスの自動機能の有効化/無効化は、この仕組みを利用して行われます。

systemdでは、Unitの設定ファイル内にサービスを起動/停止するコマンドを記載する形になります。図5.8は、httpdサービスの設定ファイル/usr/lib/systemd/system/httpd.serviceの主要部分を抜粋したもので、ExecStart、ExecReload、ExecStopが、それぞれこのサービスを起動、リロード、停止するコマンドになります。WantedByは自動起動を有効化した際に、依存関係を設定するUnitを指定します。先ほど、「systemctlenable」で自動起動を有効化した際に、multi-user.targetへの依存関係が設定されたのは、この指定によるものです。

$ cat /usr/lib/systemd/system/ssh.service
[Unit]
Description=OpenBSD Secure Shell server
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target auditd.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run

[Service]
EnvironmentFile=-/etc/default/ssh
ExecStartPre=/usr/sbin/sshd -t
ExecStart=/usr/sbin/sshd -D $SSHD_OPTS
ExecReload=/usr/sbin/sshd -t
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartPreventExitStatus=255
Type=notify
RuntimeDirectory=sshd
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target
Alias=sshd.service

歴史的には、Linuxのメモリー管理の仕組みはx86アーキテクチャーをベースに設計されてきました。現在のx86_64アーキテクチャーは、それが拡張された形になります。そこで、ここではx86アーキテクチャー、すなわちインテルアーキテクチャーの32ビット版でのメモリー管理を最初に説明します。具体的には、x86アーキテクチャーのサーバーもしくはx86_64アーキテクチャーのサーバーに、32ビット版のLinuxを導入した環境だと考えてください。

そもそも、32ビット版と64ビット版では何が違うのでしょうか? これを理解するには、「論理アドレス空間」と「物理アドレス空間」の違いを押さえる必要があります。結論を先にいうと、32ビット版と64ビット版の最大の違いは、論理アドレス空間の桁数です。32ビット版では、32ビットのアドレスを使用するので、論理アドレスに使える値は16進数で「0x00000000~0xFFFFFFFF」の範囲です。容量でいうとちょうど4GBになります。


それでは、あらためて「論理アドレス空間」と「物理アドレス空間」の違いを説明します。物理アドレス空間は、サーバーに搭載された物理メモリー領域を指定するアドレスのことです。たとえば、16GBの物理メモリーを搭載したサーバーでは、物理アドレス空間は0~16GBになります。図5.10の下部は、16GBの物理メモリーを例として、物理アドレス空間でのデータの割り当てを表しています。

カーネル自身のプログラムコード(カーネルコード)とデータ領域は、1MB~896MBの物理アドレスを使用します。この領域の物理メモリーを「Lowメモリー」と呼びます。896MB以降は、ユーザープロセスが使用するメモリーやディスクキャッシュとして使用します。この領域を「Highメモリー」と呼びます。Lowメモリーの中でカーネルが使用していない部分についても、ユーザープロセスのメモリーやディスクキャッシュとして使用されますが、その逆に、Highメモリーカーネルが使用することはありません。0~1MBの範囲については、システムBIOSなどのハードウェア機能で使用する領域として予約されています。一方、Linux上の各ユーザープロセスは、論理アドレス空間と呼ばれる0~4GBの仮想的なアドレス空間を持ちます。これが先に説明した、「0x00000000~0xFFFFFFFF」の32ビットのアドレスです。ユーザープロセスは、この論理アドレスを指定することで、メモリーへのアクセスを行います。ただし、これはあくまで仮想的なアドレスで、各アドレスに対応する物理メモリーが必ず存在するわけではありません。ユーザープロセスが論理アドレスを指定してメモリーへのアクセスを行うと、カーネルは必要に応じて物理メモリーを割り当てていきます。カーネルは、各プロセスについて、論理アドレスとそれに対応する割り当て済みメモリー物理アドレスを変換するための「変換表」を用意して管理しています。この変換表を「ページテーブル」と呼びます。また、論理アドレスに物理メモリーを割り当てることを「メモリーマッピングする」といいます。図5.10の上部は、メモリーマッピングの様子を模式的に表しています。各プロセスの論理アドレス空間では、0~3GBの領域に各プロセスが使用する物理メモリーマッピングされます。3GB~4GBの領域には、すべてのプロセスに共通で、カーネルが使用するLowメモリーマッピングされます。この3GB~4GBの領域は、ユーザープロセスを実行中のCPUが、カーネルコードに実行を切り替える際に必要となります。ユーザープロセスがシステムコールを用いてカーネルの機能を呼び出した場合など、ユーザープロセスを実行中のCPUは、カーネルによる処理が必要な際は、3GB~4GBにマッピングされたカーネルコードに処理を切り替えます。システムコールのほかに、次に説明するプロセススイッチングの際にも、カーネルコードへの処理の切り替えが行われます。

ある瞬間を捉えると、その瞬間にCPUで実行されているプロセスのページテーブルが、CPU上で有効化されています。ユーザープロセスが論理アドレスを指定すると、CPUは有効化されているページテーブルを参照して、対応する物理メモリーにアクセスを行います。論理アドレス物理アドレスに変換する処理は、MMU(MemoryManagementUnit)と呼ばれるハードウェアの機能で行われます*4。CPU上で実行するプロセスを切り替える際は、いったん3GB~4GBの領域のカーネルコードに処理が移ります。ここでカーネルは、次に実行するプロセスのページテーブルをCPU上で新たに有効化します。これで次のプロセスに処理が切り替わります。このとき、3GB~4GBの領域は、すべてのプロセスで共通にカーネルが使用するLowメモリーマッピングされているため、ページテーブルを切り替えてもカーネルコードの実行に影響はありません。

通常は、カーネルが900MB近くのメモリーを必要とすることはありませんが、メモリーを大量に使用する特殊なデバイスドライバーを使用している場合などは、まれにLowメモリーが不足して、カーネルの動作に問題が発生することがあります。物理メモリー全体に余裕があっても、Lowメモリー部分だけが不足して発生する、少しやっかいな問題です*5。この後で説明するように、x86_64アーキテクチャー、すなわち64ビット版の環境では、カーネルが使用するメモリー容量に制限はありません。最近では、64ビット版のCPUアーキテクチャーが主流になりましたので、基本的には64ビット版のLinuxを使用することをお勧めします*6。大型のデータベースシステムなど、1つのユーザープロセスが3GB以上のメモリーを必要とするアプリケーションでも、64ビット版の使用が必須となることがあります。


ここでよく、プログラマーの方から質問を受けます。たとえば、物理メモリーの空き容量が1GBの環境で動作しているプログラムコードが、malloc()関数で1GB以上のメモリーを割り当てるとどうなるでしょうか? 実は、メモリーの割り当てには成功します。この後、プログラムコードが割り当てられたメモリーに実際にデータを書き込んでいくに従って、物理メモリーが割り当てられていきます。最終的に、割り当てるための物理メモリーがどうしても確保できなくなると、後で説明する「OOMKiller」が動作します。このようなカーネルの動作を「メモリーのオーバーコミット」と呼びます。「これでは、メモリー不足がどのタイミングで発生するかわからないので、なんとかならないか」という意見をもらうことがあります。プログラマーからすると、最初にmalloc()関数でメモリーを割り当てたタイミングでエラーになったほうが、例外処理の対応が簡単になるからです。この点は、カーネル開発者の間でも長く議論されてきました。結論としては、「もしものために、大量のメモリーmalloc()して、ほとんど使用しないプログラムがたくさんあるので、メモリーのオーバーコミット機能は役に立つ」という理由で、オーバーコミットの仕組みが残っています。


これまでに説明したページテーブルによるメモリーマッピングの仕組みは、x86_64アーキテクチャーでも基本的には同じです。x86_64アーキテクチャーで異なるのは、論理アドレスに64ビットのアドレスを使用するという点です。64ビットのアドレス空間は、容量でいうと16EB(エクサバイト)あるので、実質的には無限の大きさです。したがって、4GBの論理アドレスを3GBと1GBに分割するような必要はなく、前述のようなメモリーサイズの制限が発生しません。物理メモリーの容量が許す限り、ユーザープロセスも、カーネルも、必要なだけのメモリーを使用することができます。また、x86_64アーキテクチャーの環境では、LowメモリーとHighメモリーの区別がありません。すべての物理メモリーが、Lowメモリーとして取り扱われます。


Linuxは、物理メモリーの空き容量の大部分をディスクキャッシュとして使用します。ディスクキャッシュ上のデータは、ファイルアクセスが完了した後も解放されずに残るので、大容量のファイルアクセスを行った後はメモリーの空き容量が大きく減少したように見えます。ただし、プロセスが必要とするメモリーが不足した場合は、ディスクキャッシュは適宜解放されていきます。したがって、実質的なメモリーの空き容量については、ディスクキャッシュの部分は空き容量と見なして考える必要があります。


Linuxは、物理メモリーの空き容量の大部分をディスクキャッシュとして使用します。ディスクキャッシュ上のデータは、ファイルアクセスが完了した後も解放されずに残るので、大容量のファイルアクセスを行った後はメモリーの空き容量が大きく減少したように見えます。ただし、プロセスが必要とするメモリーが不足した場合は、ディスクキャッシュは適宜解放されていきます。したがって、実質的なメモリーの空き容量については、ディスクキャッシュの部分は空き容量と見なして考える必要があります。

$ free
               total        used        free      shared  buff/cache   available
Mem:         3897284      681468     1831308        5736     1384508     2993364
Swap:        1048576           0     1048576

「total」は物理メモリー全体の容量で、「used」は何らかの目的で使用されている容量、そして、「free」はまったく使用されていない容量です。一方、「available」は実質的な空き容量を示します。前述のように、必要に応じて解放されるディスクキャッシュなどを差し引いた容量です。「buff/cache」がディスクキャッシュとして使用中の容量です。ディスクキャッシュの中には、何らかの理由で解放できない部分があるため、「available」には実際に解放可能な容量だけを差し引いた値が表示されます。

ディスクのスワップ領域は、メモリーの空き容量が不足した際に、物理メモリーの一部をスワップ領域に退避して必要な空き容量を確保する仕組みです。スワップ領域に退避した内容は、プロセスからのアクセスが発生すると、再び物理メモリーに読み込まれます。物理メモリースワップ領域に書き出すことを「スワップアウト」、スワップ領域から物理メモリーに読み込むことを「スワップイン」と呼びます。

デバイスドライバーは、カーネルに組み込まれるカーネルモジュールなのでカーネルのメモリー領域を使用します。また、カーネルのメモリー領域は、スワップ領域にスワップアウトされることもありません。


ext4およびXFSファイルシステムは、ファイルシステムとして使用するディスク領域全体を複数のグループに分割して、各グループに対して「メタデータ」と「実データ」を配置します(図5.13)。それぞれのグループは、「ブロックグループ」(ext4ファイルシステム)、もしくは、「アロケーショングループ」(XFSファイルシステム)と呼ばれます。メタデータは、ファイルシステム全体の設定や個々のファイルのカタログ情報で、一方、実データは実際のファイルの内容です。実データを配置する領域を「データブロック」といいます。

ファイルシステム管理の上で理解しておくとよいメタデータには、「スーパーブロック」と「inode領域」があります。スーパーブロックは、ファイルシステム全体の設定情報を記録するもので、ファイルシステムのオプションパラメーターもスーパーブロックに記録されます。スーパーブロックが破損すると、ファイルシステム全体が使用できなくなるため、ファイルシステム内の複数の領域に同一のスーパーブロックが記録されます。ファイルシステムの整合性をチェックするfsck.ext4コマンド、あるいはxfs_repairコマンドは、複数のスーパーブロックの内容を比較したり、1つ目のスーパーブロックが破損している際に、2つ目のスーパーブロックを用いて破損を修復するなどの動作を行います。inode領域には、個々のファイルの情報(アクセス権、タイムスタンプ、データブロックへのポインターなど)が記録されます。1つのファイルに対して1つのinodeを使用します。ext4ファイルシステムでは、inode領域の割合をファイルシステムの作成時に指定します。inode領域が不足すると、データブロックに空き容量があったとしても、それ以上ファイルが作成できなくなります。ファイルシステム内に多数のファイルを保存する見込みがある場合は、最初に十分なサイズのinode領域を確保する必要があります。一方、XFSファイルシステムでは、必要に応じてinode領域が確保されるようになっているのでこのような心配はありません。また、ext4およびXFSファイルシステムの両方において、「inodeサイズ」には注意を払う必要があります。これは、個々のinodeの大きさを指定するもので、典型的には、128バイト、256バイト、512バイトなどの値をファイルシステムの作成時に指定します。ext4およびXFSファイルシステムでは、それぞれのファイルについて、SELinuxのコンテキスト情報やACL(AccessControlList)などの拡張ファイル属性を設定できますが、これらの情報はinodeに保存されます。多数の拡張ファイル属性を設定したファイルにおいて、inodeサイズが不足すると、内部的に複数のinodeを使用する必要がありアクセス性能が低下します。たとえば、inodeサイズが256バイトの場合、1つのファイルについて約100バイトの拡張ファイル属性が保存可能です。


ファイルシステム上のファイルへの書き込みは、Linuxのディスクキャッシュを経由して行われます。ファイルの更新内容は、サーバーのメモリー上にあるディスクキャッシュに書き込まれた後、定期的にまとめて物理ディスクに書き込まれます。このとき、たとえば、メタデータをディスクキャッシュから物理ディスクに書き込んでいる途中で、突然サーバーが停止したとします。すると、ディスク上には中途半端に更新されたメタデータが残るため、メタデータの不整合が発生して、ファイルシステムが正しく利用できなくなります。ジャーナリングファイルシステムは、ジャーナルログを利用することで、このようなメタデータの不整合が発生した際に、確実に整合性を回復する方法を提供します。ext4およびXFSファイルシステムは、どちらもジャーナリングファイルシステムになっています。

第6章Linuxサーバーの問題判別

6.1.3システムログの収集

Linux上で稼働するユーザープロセスのログメッセージの出力先は、大きく次の3つに分かれます。

①独自のログファイル②標準出力/標準エラー出力③syslogメッセージ①はプロセスが独自にログファイルを用意して、そこに出力するメッセージです。これは、journaldやrsyslogdの管理対象とはなりません。②は、一般には、プロセスを起動した端末画面に表示されるメッセージになります。バックグラウンドで稼働するデーモンプロセスの場合、これまでの環境では無視して破棄されるように設定されることがほとんどでした。ただし、systemdのUnitとして起動したサービスのプロセスについては、②のメッセージについてもjournaldが受け取るようになっていて、journaldのログデータベースに保存されます*2。③は、プロセス内のプログラムコードがsyslog()関数で出力するメッセージで、一般にsyslogメッセージと呼ばれるものです。③については、journaldが受け取ってログデータベースに保存した後、さらにrsyslogdにも同じメッセージを転送します。以上の結果、rsyslogdは、すべてのプロセスのsyslogメッセージを受け取ることになります。これらのメッセージを設定ファイル/etc/rsyslogd.confに基づいて、各種のログファイルに出力します。一方journaldは、すべてのプロセスのsyslogメッセージに加えて、サービスプロセスの標準出力/標準エラー出力の内容を受け取って、これらを独自のログデータベースに保管していきます。journaldのログデータベースの内容は、journalctlコマンドで検索することが可能です。ただし、journaldのログデータベースは、サーバーを再起動するとその内容が失われます*3。稼働中のサービスの状況確認などはjournalctlコマンドで行い、問題発生時にサーバー再起動前の古い情報を確認する際は、rsyslogdが出力したログファイルを参照するといった使い分けが必要です。


カーネルおよびカーネルモジュール(デバイスドライバー)が出力するログメッセージは、はじめにカーネル内部のカーネルログバッファ(メモリー上のバッファ領域)に保存されます。これは、journaldやrsyslogdが稼働していない状態でも、カーネルからのログメッセージを失わないための仕組みです。特にシステム起動時は、journaldやrsyslogdが起動する前の段階から、カーネルはログメッセージを出力します。これらは、いったんカーネルログバッファに蓄積された後に、journaldとrsyslogdが起動したタイミングでログファイルに出力されます。現在のカーネルログバッファの内容は、dmesgコマンドで確認できます。また、システム起動直後のカーネルログバッファの内容は、ログファイル/var/log/dmesgにも記録されています。

この仕組みに関連して、システム起動時のカーネルログの見方について、少し注意が必要です。journaldとrsyslogdは、システム起動時にsystemdのUnit(サービス)として起動します。これらが起動したタイミングで、それまでカーネルログバッファに蓄積されたメッセージがまとめてログファイルに出力されます。このため、システムログファイル/var/log/messagesからシステム起動時のメッセージを確認すると、同一のタイムスタンプを持ったカーネルログがまとめて記録されています。これは、これらのログが実際に出力された時刻ではなく、あくまでjournaldとrsyslogdが起動してログファイルに書き出した時刻になります。

以上