えんでぃの技術ブログ

えんでぃの技術ブログ

ネットワークエンジニアの視点で、IT系のお役立ち情報を提供する技術ブログです。

「はてなブロガーに10の質問」をやってみました

thumbnail

概要

はてなブログ10周年特別お題「はてなブロガーに10の質問」企画に乗っかってみました。
参加賞のレッドスター10個が欲しい...というのが動機になります。

参加条件は、「10にまつわる4つのお題」というキーワードを挿入することとのことです。

ほぼ初の非技術トピックでの投稿になりますが、よろしければざっと読んでいただければと思います。

キャンペーンの参加期限は、2021/11/24 (水) までです。
興味のある方はどうぞ。

blog.hatena.ne.jp

では、本題に入ります。

回答

ブログ名もしくはハンドルネームの由来は?

大学時代に呼ばれていたニックネームです。

はてなブログを始めたきっかけは?

相当苦労しながらネットワークシミュレータ (GNS3) や、その前提となるLinuxの基礎知識を手元のメモにまとめていた時期がありました。
そのノウハウを他の方とも共有して、誰かの役に立ったらモチベーションに繋がると思ったのがブログ開設のきっかけでした。

...そういえば、GNS3はおろか、ネットワーク関連の記事をまだ投稿していませんでした。

自分で書いたお気に入りの1記事はある?あるならどんな記事?

強いて言うならこちらです。

endy-tech.hatenablog.jp

NUCの購入をきっかけにLinuxの面白さ、便利さにずぶずぶとハマっていきました。
今思えば、このシリーズの記事を書いていたときが一番わくわくしていました。
思い出深いです。

KVMやsystemd-resolved、rsyncなどの超長文記事は、この出来事をきっかけに生まれました。

ブログを書きたくなるのはどんなとき?

自分にとって新しい分野や製品について色々な情報ソースを見て回り、ある程度包括的な理解を得られたときです。

技術を学ぶ際、一番学習が大変なのは学び始めだと思います。
Getting Started Guide が整備された製品であればよいのですが、公式ドキュメントからシンプルな理解を得ることが難しい製品も数多くあります。

私が休日に学んでブログ記事にまとめるトピックは、大抵本業とは離れています。
ゆくゆく本業で取り扱うようになったとき、同僚たちには効率よくキャッチアップしてほしい。
同僚でなくても、似たような立場で公式ドキュメントの一歩手前の入門知識が欲しい方に役立って欲しい。
そう思いながら記事を書いています。

学びたい動機、学ぶ時間、そして私がカバーしたいと思っている学ぶための前提知識さえ持っていれば、難しい知識は自力で学んでいけると思います。

下書きに保存された記事は何記事? あるならどんなテーマの記事?

下書きにあるのは1記事です。
頃合いを見て投稿します。

Linuxのディスク拡張に関する記事です。
KVMのqcow2、パーティション、LVM、ファイルシステムと拡張するところがたくさんあり、地味に労力がかかるのです。

自分の記事を読み返すことはある?

あります。
ブログも一種の補助記憶で、書いたことは大抵忘れます。

ただ「書いた」という事実は記憶しているので、1年後か2年後か、また必要な機会があれば読み返します。

あと、スターをもらったときとかもしばしば読み返します (笑)。

好きなはてなブロガーは?

たくさんいますが、第一印象で...。

今の時点で技術的な興味のある分野が近いので、よく観に行きます。
Ansible関連のブログもそろそろ書きたいなぁと思い始めています。

tekunabe.hatenablog.jp

zaki-hmkc.hatenablog.com

usage-automate.hatenablog.com

taso-int.hatenablog.com

将来的に使いそうな技術の参考になります。
最近は、インフラエンジニアの視点でPythonAWSのノウハウを共有してくださっています。
文章が簡潔で説明が見やすいので、ブログの書き方の非常に良い勉強になっています。
デザインや色調も好みです。
最近wordpressに移行されたとのことですが、こちらもRSSTwitterで更新に気づいたら見に行っています。

friendsnow.hatenablog.com

非技術ブログです。
私は外に出ることが少ない方なので、行ったことのない場所を写真付きで教えてくれるのが嬉しいです。
実は、今回の「10の質問」企画もこの方のエントリで知りました。
ありがとうございます。
投稿主はUbuntuを使っていたとのことですが、Fedoraと比べてどうなのかそのうち聴いてみたいです。

opll-inaka.hatenablog.com

はてなブログに一言メッセージを伝えるなら?

トピックや容量に縛りがない無料ブログということで大変重宝しています。
これからもよろしくお願いします。

この10年を一言でまとめると?

読者の皆さまに支えられて続けてこれました。
本当にありがとうございます。

この10年を10文字でまとめると?

Ansibleが好き

KVMの基本操作集

linux-kvm-logo
引用元:linux-kvm.org

前の記事

本記事では、KVMの初期設定が完了していることを前提としています。
初期設定手順に興味のある方は、以下の記事もご参照ください。

endy-tech.hatenablog.jp

virt-manager、Cockpitのセットアップについては、以下の記事をご参照ください。

endy-tech.hatenablog.jp

endy-tech.hatenablog.jp

お伝えしたいこと

KVMの基本操作をケース別に紹介します。

基本はCLIの手順紹介がメインです。
virt-managerとCockpitのスクリーンショットも載せていますが、今後画面のデザインが変わっても原則更新しません。

GUIの手順については、雰囲気を掴むためのおまけと思っていただければ幸いです。

VMの作成

virt-installで行う場合

過去記事を参照してください。

virt-managerで行う場合

過去記事を参照してください。

Cockpitで行う場合

過去記事を参照してください。

VMの削除

virshで行う場合

過去記事を参照してください。

virt-managerで行う場合

VMを右クリックして "Delete" します。

virt-manager_vm_deletion1

virt-manager_vm_deletion2

Cockpitで行う場合

仮想マシン一覧の画面で … をクリックして "Delete" します。

cockpit_vm_deletion1

cockpit_vm_deletion

VMの起動

virshで行う場合

VMの起動にはvirsh startを使います。
コマンドがわかりやすいので、普段GUIを使っていてもvirsh startだけはCLIといった使い方ができるかなと思います。

virsh start vm_name

以下に具体例を示します。

# VMの一覧を表示
virsh list --all
#  -    fedora33                             shut off

virsh start fedora33
# Domain 'fedora33' started

virt-managerで行う場合

トップ画面で対象のVMを右クリック → Runで起動します。

virt-manager_start_vm

Cockpitで行う場合

仮想マシン一覧の画面で対象のVMを探し、"Run"をクリックします。

cockpit_start_vm

VMの停止

virshで行う場合

VMの停止にはvirsh shutdownを使います。
このコマンドはshutdownシーケンスがちゃんと走るので、VMに優しいです。

VMがハングしている場合や、OSインストール画面などOS起動前の状態など、shutdownを受け付けないケースもありえます。
そんなときは、virsh destroyを使います。 destroyというとVMを破壊しそうですが、実際にはVMの強制終了を意味します。
ニュアンスとしては「VMプロセスをdestroy (kill) する」のであって、VMXML定義ファイルには触れません。

virsh shutdown fedora33
virsh destroy fedora33

virt-managerで行う場合

トップ画面で対象のVMを右クリック → Shut Down → Shut Down でシャットダウンします。
右クリック → Shut Down → Forced Off で強制終了します。

virt-manager_stop_vm

Cockpitで行う場合

仮想マシン一覧の画面で対象のVMを探し、Shut downでVMをシャットダウンします。
… をクリックして "Force shut down" でVMを強制終了します。

cockpit_stop_vm

VMのクローン

virt-cloneで行う場合

virt-cloneコマンドでクローンできます。

前提として、まずはVM名を調べておきます。
virsh listはデフォルトで起動しているVMしか表示しませんが、--allをつけることで全てのVMを表示できます。

virsh list --all
#  Id   Name         State
# ---------------------------
#  -    fedora33     shut off

fedora33というVMをクローンします。
新しいVMの名前は、f33_newとします。

virt-clone --original fedora33 --name f33_new --auto-clone 
# Allocating 'f33_new.qcow2'     |  20 GB  00:00:05

# Clone 'f33_new' created successfully.

virt-cloneの主なオプションは以下のとおりです。
--originalでコピー元のVM名、--nameでコピー先のVM名、--fileでコピー先のVMイメージファイルのフルパスを指定するのが基本です。
個人的には、上記のように--name--auto-cloneを併用し、イメージファイル名を--nameで指定した文字列から自動生成させるのが便利に感じました。

オプション 意味
-o,
--original
コピー元のVM名を指定
-n,
--name
コピー先のVM名を指定
-f,
--file
コピー先のVMが利用するディスクイメージファイルのフルパスを指定
--auto-clone --name--fileの片方または両方を自動的に決定する。
--nameを自動生成するときは、fedora33-cloneのように-cloneをつける。
更にクローンすると、fedora33-clone-1, fedora33-clone-2, ...と連番をつける。
--fileを自動補完した場合、defaultプール配下にVM名.qcow2が生成した(※)。
--nameのみ明示的に指定した場合は、イメージファイル名の自動補完には--nameの文字列が使用された(※)

(※) man virt-cloneにはこの仕様は書いていません。私の環境で実機検証した結果、このようになりました

virt-clonevirt-installパッケージに同梱されています。
dnf provides virt-cloneや、dnf repoquery -l virt-installなどで確認できます。

virt-managerで行う場合

VM名を右クリックして "Clone" します。

virt-manager_clone1

virt-manager_clone2

Cockpitで行う場合

仮想マシン一覧の画面で … をクリックして "Clone" します。

cockpit_clone1

cockpit_clone2

CDドライブにISOファイルをセットする

virshで行う場合

まずはVM名を指定して、紐付いているデバイス名を確認します。
VM名を調べたい場合は、virsh list --allを先に実行してください。

以下のコマンド出力から、sdaにCDドライブが存在することがわかりました。

virsh domblklist fedora33 --details
#  Type   Device   Target   Source
# -------------------------------------------------------------------
#  file   disk     vda      /home/shared/kvm_volumes/fedora33.qcow2
#  file   cdrom    sda      -
#  file   cdrom    sdb      -

virsh change-mediaコマンドにより、CDROMにISOファイルをセットします。

virsh change-media fedora33 sda /home/shared/images/fedora/Fedora-Server-dvd-x86_64-33-1.2.iso

# Successfully updated media.

もう一度確認コマンドを実行し、ISOファイルがセットされたことを確認します。

virsh domblklist fedora33 --details
#  Type   Device   Target   Source
# -----------------------------------------------------------------------------------------
#  file   disk     vda      /home/shared/kvm_volumes/fedora33.qcow2
#  file   cdrom    sda      /home/shared/images/fedora/Fedora-Server-dvd-x86_64-33-1.2.iso
#  file   cdrom    sdb      -

virsh change-mediaの構文は以下のとおりです。

virsh change-media domain-name path [--eject|--insert|--update] [source]

キーワードの意味は下表のとおりです。

キーワード 説明
domain-name domain、つまりVMの名前
path 捜査対象のCDドライブのデバイス名。
sda, vda, hda などの文字列が入る
--eject CDドライブの中身を空にする。
sourceの指定は不要
--insert CDドライブにISOファイルをセットする。
sourceの指定は必須
--update sourceを指定した場合、--ejectしてから--insertする。
sourceを指定しなかった場合、--ejectのみ実行する。
--eject, --insert, --update をいずれも指定しなかった場合、デフォルトで--updateが選択される
source ISOファイルを指定する。
--ejectを指定した場合は、sourceの入力は不要。
--insertを指定した場合はsourceの入力は必須。
--updateを指定した場合は、sourceを入力するか否かで挙動が変わる

virt-managerで行う場合

VMの詳細画面で、"SATA CDROM"からISOファイルのパスを指定します。

virt-manager_insert_cd

Cockpitで行う場合

仮想マシン一覧の画面で、捜査対象の仮想マシン名をクリックして詳細画面を開きます。
中段の "Disks" セクションの "Add disk" をクリックし、以下のように選択します。

設定項目 詳細
Source Custom path
Custom path (ISOファイルのパス)
Device CD/DVD dusk

cockpit_add_disk1

cockpit_add_disk2

起動順序の設定

virshで行う場合

HDD、CDドライブなど複数のディスクを持ったVMを起動する際の起動順序の制御方法を紹介します。

起動順序を制御するためのvirshサブコマンドは、2021年時点では存在しないようです。
従って、virsh edit VM_NAMEXMLファイルを直接編集します。

編集は以下のように行います。

  1. <os>セクション配下の<boot>セクションを削除する
  2. <disk>セクション配下に<boot order>セクションを追加する

例えば、編集前のファイルが以下の状態だったとします。

(中略)
  <os>
    <type arch='x86_64' machine='pc-q35-5.1'>hvm</type>
    <boot dev='hd'/>
  </os>
(中略)
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/home/shared/kvm_volumes/fedora33.qcow2'/>
      <backingStore/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='sda' bus='sata'/>
      <readonly/>
      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
    </disk>
(中略)

編集後のファイルは、以下のようになります。
<os>配下の<boot>を削除し、<disk>配下に<boot order>を追加しました。
<boot order>には'1'以上の値を指定し、値が小さいほど優先順位が高くなります。
今回の場合は、device='disk'が第一優先で、device='cdrom'が第二優先になっています。
<boot order>が付いていないデバイスは、そもそも起動デバイスの候補から外れます。

(中略)
  <os>
    <type arch='x86_64' machine='pc-q35-5.1'>hvm</type>
  </os>
(中略)
  <devices>
    <emulator>/usr/bin/qemu-system-x86_64</emulator>
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2'/>
      <source file='/home/shared/kvm_volumes/fedora33.qcow2'/>
      <backingStore/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x0'/>
      <boot order='1'/>
    </disk>
    <disk type='file' device='cdrom'>
      <driver name='qemu' type='raw'/>
      <target dev='sda' bus='sata'/>
      <readonly/>
      <address type='drive' controller='0' bus='0' target='0' unit='0'/>
      <boot order='2'/>
    </disk>

手順としては以上です。

最後に、libvirt公式サイトのリンクを貼ります。

libvirt.org

公式に書いてあるように、<boot order>が使えるかどうかはハイパーバイザー次第のようです。
幸いにも、QEMU<boot order>に対応していました。

<boot order>より以前にある指定方法として<boot>もありますが、こちらは理由がなければ基本使いません。
Disk、CDといった粒度で起動の優先順位を指定することはできますが、Diskが2つ以上存在した時、どちらのDiskを使うかの挙動が複雑です。
可能な限り、DiskやCDが同種類で複数存在する場合でも優先順位を指定できる<boot order>を使うようにしましょう。
<boot>に関する情報は以下のリンクにあります。

libvirt.org

virt-managerで行う場合

GUIを使えば、virshよりも簡単な操作で設定できます。

以下のVMの設定画面で、起動デバイスに設定したいデバイスにチェックを入れ、▲▼で優先順位を指定するだけです。
※以下の画像は▲▼が黒くて見えづらいので若干合成してあります

virt-manager_boot_order

Cockpitで行う場合

仮想マシンの詳細画面を開き、"Boot order" の "edit" をクリックして編集するだけです。
こちらも簡単ですね。

cockpit_boot_order1

cockpit_boot_order2

仮想ネットワークの作成

概要

仮想ネットワークにはいくつかのタイプがあります。
私が使ったことがあるのは、以下の2種類です。
多くの場合はNATのみで事足りますが、VM間通信用のネットワークセグメントを複数作成したい場合はNoneのネットワークも1つ追加すると便利です。

  • NAT: 外部通信とVM間通信が可能
  • None: VM間通信のみ可能

また、仮想ネットワーク内でDHCPを使うことができます。
DHCPにより、作成したVMに自動的にIPアドレスデフォルトゲートウェイの設定を配布できます。
ネットワークサイズが/24だとすると、DHCPアドレスを.101〜.254のようにしておくと、.2〜.100を静的に割り当てることもできるので便利です。
.1はゲートウェイのアドレスに使われるので、この部分は残しておきます。

さて、以降で具体的な手順に入ります。

virshで行う場合

CLIの手順はlibvirt wikiを参考にしました。

net-createというコマンドがありますが、xmlファイルを指定することでしか作成できません。
従って、基本的には既存のXMLファイルを雛形にして編集することになると思います。

まず、virsh net-list --allで仮想ネットワーク名を確認します。
--allをつけることで、起動していない仮想ネットワークも表示します。

virsh net-list --all
#  Name        State    Autostart   Persistent
# ----------------------------------------------
#  default     active   yes         yes
#  internal1   active   yes         yes

新規の仮想ネットワークを作る場合は、/usr/share/libvirt/networks/default.xmlを雛形にすると便利です。
以下のようにXMLファイルをコピーします。

cp /usr/share/libvirt/networks/default.xml tmp.xml
cat tmp.xml
# <network>
#   <name>default</name>
#   <bridge name="virbr0"/>
#   <forward/>
#   <ip address="192.168.122.1" netmask="255.255.255.0">
#     <dhcp>
#       <range start="192.168.122.2" end="192.168.122.254"/>
#     </dhcp>
#   </ip>
# </network>

既に作成済みの仮想ネットワークを雛形にする場合は、virsh net-dumpxmlで既存の仮想ネットワークのXMLファイルをtmp.xmlとして保存しておきます。
<uuid><mac address>の部分はコピー元のネットワークと値が被ってはいけないので削除し、自動生成に任せるようにしてください。

virsh net-dumpxml default | tee net-default.xml
# <network connections='1'>
#   <name>default</name>
#   <uuid>b130cf72-a5eb-4414-9fd8-7d1182f2b50b</uuid>
#   <forward mode='nat'>
#     <nat>
#       <port start='1024' end='65535'/>
#     </nat>
#   </forward>
#   <bridge name='virbr0' stp='on' delay='0'/>
#   <mac address='52:54:00:96:e6:67'/>
#   <ip address='192.168.122.1' netmask='255.255.255.0'>
#     <dhcp>
#       <range start='192.168.122.101' end='192.168.122.254'/>
#     </dhcp>
#   </ip>
# </network>

<name>を仮想ネットワーク名に、仮想ブリッジインターフェース名を被らないよう連番で指定、その他IPアドレスなどを書き換えてください。
書き換え後の例を以下に示します。
ちなみにlibvirt wikiによると、<forward/>の部分は<forward mode='nat'/>がデフォルト値となります。
NATモードではなく、None (Isolated) モードにしたい場合は、<forward/>行を削除してください。

cat tmp.xml
# <network>
#   <name>tmp</name>
#   <bridge name="virbr2"/>
#   <forward/>
#   <ip address="192.168.200.1" netmask="255.255.255.0">
#     <dhcp>
#       <range start="192.168.200.101" end="192.168.200.254"/>
#     </dhcp>
#   </ip>
# </network>

以下のコマンドにより、仮想ネットワークを作成します。
net-defineコマンドの場合は、XMLファイルとして構成を保持します。
net-definenet-createに置き換えることで、undefined状態 (メモリ上にしか存在せず、destroyすると直ちに消える) の仮想ネットワークを作成することもできます。

virsh net-define tmp.xml
# Network tmp defined from tmp.xml

作成したtmpの情報を表示します。
net-listの出力より、tmpの定義直後はまだ起動しておらず、OSブート時に自動起動もしないことがわかります。
net-dumpxmlの出力より、UUIDやMACアドレスの値、NAT設定などが自動生成していることがわかります。

virsh net-list --all
#  Name        State      Autostart   Persistent
# ------------------------------------------------
#  default     active     yes         yes
#  internal1   active     yes         yes
#  tmp         inactive   no          yes

virsh net-info tmp
# Name:           tmp
# UUID:           3bfd3e32-9531-4e67-a2b0-963df9f4cc85
# Active:         yes
# Persistent:     yes
# Autostart:      no
# Bridge:         virbr2

virsh net-dumpxml tmp
# <network>
#   <name>tmp</name>
#   <uuid>85a1cccf-dc54-4164-8e72-a6f020b4883c</uuid>
#   <forward mode='nat'/>
#   <bridge name='virbr2' stp='on' delay='0'/>
#   <mac address='52:54:00:55:21:d6'/>
#   <ip address='192.168.200.1' netmask='255.255.255.0'>
#     <dhcp>
#       <range start='192.168.200.101' end='192.168.200.254'/>
#     </dhcp>
#   </ip>
# </network>

以下のコマンドによって仮想ネットワークを起動し、通信可能な状態にします。
更に、自動起動も有効化します。

virsh net-start tmp
# Network tmp started

virsh net-autostart tmp 
# Network tmp marked as autostarted

再び確認コマンドで表示します。
State=active, Autostart=yesとなりました。

virsh net-list --all
#  Name        State    Autostart   Persistent
# ----------------------------------------------
#  default     active   yes         yes
#  internal1   active   yes         yes
#  tmp         active   yes         yes

仮想ネットワークを削除する際は、destroy (強制終了。startの反対) とundefine (設定削除。defineの反対) を行います。

virsh net-destroy tmp
# Network tmp destroyed

virsh net-undefine tmp
# Network tmp has been undefined

削除後、tmpが表示されなくなることを確認します。

virsh net-list --all
#  Name        State    Autostart   Persistent
# ----------------------------------------------
#  default     active   yes         yes
#  internal1   active   yes         yes

virt-managerで行う場合

トップ画面において、コネクション、またはコネクション配下のVMを選択した状態で、上部メニューからEdit > Connection Detailsを開きます。
または、下図赤枠部分のコネクション名の部分を右クリックして、Detailsを選択します。

virt-manager_connection_details

Virtual Networksタブを開き、左下の+ボタンから仮想ネットワークを追加します。
※以下の画像は+-が黒くて見えづらいので若干合成してあります

virt-manager_add_network1

virt-manager_add_network2

Cockpitで行う場合

仮想マシン一覧から、右上の"Networks"を選択します。

cockpit_add_network1

右上の"Create virtual network"を選択します。

cockpit_add_network2

必要事項を入力して、Createします。
Cockpit 249では、Forward modeがNAT、Open、None (isolated mode) の3種類しか選べませんでした。
将来的にアップデートされるとは思いますが、もし機能的に困る場合はvirshかvirt-managerを使うようにしてください。

私の検証用途では凝ったことはしないので、私としてはそれほど困りませんでした。

cockpit_add_network3

ストレージプールの作成

ストレージプールという言葉自体の解説は、KVMの初期設定、及びvirsh, virt-installによるVM作成 - pool 作成の事前準備を参照してください。

virshで行う場合

以下のコマンドでPoolを定義します。
Pool名、Poolと紐付けるディレクトリパスは適宜変更してください。

virsh pool-define-as kickstart dir --target /home/shared/libvirt/images/kickstarts

virsh pool-define-asのSyntaxは以下のとおりです。

パラメータ 意味
kickstart
(第一引数)
Pool名
dir
(第二引数)
Pool Type名。
他にもfsdiskなど多数あるが、本ブログで紹介したのはdirのみ (参考)
--target .. dir Typeの場合は指定必須。
Poolと紐付けるディレクトリパスを指定する

作成したPoolを確認します。
作成直後のPoolは停止しているので、--allオプションをつけることで停止中のPoolも含めて表示します。

virsh pool-list --all
#  Name        State      Autostart
# ---------------------------------
#  default     active     yes
#  kickstart   inactive   no

Poolを今すぐ起動します。
また、Autostartを有効化することで、OS起動時にPoolも自動起動するように設定します。

virsh pool-start kickstart
# Pool kickstart started

virsh pool-autostart kickstart
# Pool kickstart marked as autostarted

Poolの状態を再度確認します。
Poolが起動状態になり、Autostartも有効化されました。

virsh pool-list
#  Name        State    Autostart
# ---------------------------------
#  default     active   yes
#  kickstart   active   yes

Poolの停止はvirsh pool-destroy kickstart、Poolの定義削除はvirsh pool-undefine kickstartコマンドにて行います。

virt-managerで行う場合

トップ画面において、コネクション、またはコネクション配下のVMを選択した状態で、上部メニューからEdit > Connection Detailsを開きます。
または、下図赤枠部分のコネクション名の部分を右クリックして、Detailsを選択します。

virt-manager_connection_details

Storageタブを開き、左下の+マークを選択します。

virt-manager_add_pool1

必要なパラメータを入力し、Finishを選択します。

virt-manager_add_pool2

Cockpitで行う場合

仮想マシン一覧の画面で、左上にあるStorage poolsのリンクをクリックし、Pool一覧の画面を開きます。

cockpit_add_pool1

画面右上のCreate storage poolをクリックし、Pool作成画面を開きます。

cockpit_add_pool2

必要なパラメータを指定してPoolを作成します。
以下は/home/shared/libvirt/images/kickstarts/ディレクトリと紐づく、kickstartという名前のPoolを作成する例です。

cockpit_add_pool3

Activateボタンをクリックし、作成したPoolを起動します。

cockpit_add_pool4

仮想ディスクイメージファイルの拡張

ディスクファイル (qcow2) のサイズ変更は、2021/11時点ではCLIでしかできないようです。
したがって、virshの手順のみ紹介します。

virshで行う場合

Linuxのディスク拡張手順 #qcow2ファイルの拡張を参照してください。

(参考) DHCPで割り当てているIPアドレスの一覧表示

virshで行う場合

virsh net-dhcp-leases <network-name>DHCPリースしているIPアドレスを一覧表示できます。
VMIPアドレスDHCPで設定しているとき、VMSSHする前にIPアドレスを調べる際に便利です。

ネットワークの一覧はvirsh net-listで確認できますが、多くの場合はdefaultを使っているのではないかと思います。

virsh net-dhcp-leases default
#  Expiry Time          MAC address        Protocol  IP address          Hostname   Client ID or DUID
# ------------------------------------------------------------------------------------------------------
#  2022-02-27 13:41:03  52:54:00:6c:54:c8  ipv4      192.168.122.127/24  stream9-1  01:52:54:00:6c:54:c8

よく使うコマンドなので、ワンライナーをaliasとして登録するのも便利かもしれません。
私の環境では、以下の内容を~/.bashrcに登録しています。

alias ips=$'virsh net-dhcp-leases default | sed -ne \'3,$p\' | grep -v ^$ | awk \'gsub(/\/[0-9]+/, "", $0) { print $6,$5 }\''

実行すると以下のようになります。
短い入力でコピペしやすいシンプルな出力が得られます。

ips
# stream9-1 192.168.122.127

上記はANSI C Quoting ($' ')を使っています。
通常のシングルクォーテーション (' ')と比較して、シングルクォーテーション自体を \'エスケープできるのが便利なので使っています。

bash以外の環境ではANSI C Quotingが使えないかもしれないので、ダブルクォーテーションを使ったパターンも併記しておきます。
ダブルクォーテーションを使う場合は$"\、そしてバックティックもエスケープ対象になるので、少々見づらくなります。
(参考: Double Quotes)

#alias ips="virsh net-dhcp-leases default | sed -ne '3,\$p' | grep -v ^\$ | awk 'gsub(/\/[0-9]+/, \"\", \$0) { print \$6,\$5 }'"

virt-managerで行う場合

virt-managerには、DHCPリースを一覧表示する機能は無いようです。

仮想マシンの詳細設定画面でNICの項目から読み取ることはできます。
しかし、仮想マシン1台分の情報しか確認できません。

virt-manager_nic

Cockpitで行う場合

Cockpitには、DHCPリースを一覧表示する機能は無いようです。

仮想マシンの詳細設定画面でNetwork Interfacesの項目から読み取ることはできます。
しかし、仮想マシン1台分の情報しか確認できません。

cockpit_network_interfaces

まとめ

KVM周りの各種基本操作についてご紹介しました。

慣れないうちはGUIで、慣れてきたらVMの起動/停止など簡単な部分からCLIに移行し、徐々に作業効率を上げていきましょう。

sudo pip を実行してはいけない理由

python-logo

お伝えしたいこと

sudo pipを実行すべきではないという話を書きます。

詳しくは書きませんが、ローカルのVMsudo pip uninstallを実行した結果、状況次第ではおかしなことになりました。
検証の結果、わかったことをかいつまんでお伝えします。

※元々別記事に書いてあった内容ですが、記事を分けました

sudo pip install が NG である理由

sudo pip installは実行しないでください。
Fedora Wikiでも、以下のように力強く書かれていました。

Never ever ever ever use pip or pip3 with sudo. Use pip --user or Python virtual environments instead.

Fedora Wiki

実行してはいけない理由は2つあります。

  • システムを破壊する懸念がある
  • 知らぬ間にvenvの外側を破壊するケースがある

詳細はこの後に続きます。

システムを破壊する懸念

pipには、--system--userの2種類があります。

pip install --systemは、システム全体にPythonパッケージをインストールするというオプションです。
デフォルトのインストール先は/usr/local/配下です。
具体的には/usr/local/bin/や、/usr/local/lib/pythonX.Y/site-packages/配下です。

pip install --userは、ユーザーごとに個別にPythonパッケージをインストールするという意味です。
デフォルトのインストール先は、~/.local配下です。
具体的には~/.local/bin/や、~/.local/lib/pythonX.Y/site-packages/配下です。

pip listを実行すると、実は--system--userの両方をマージした結果が出力されます。
pip list --userを実行すると、--userの結果のみが表示されます。

pip listの仕組みを正確に理解しているわけではありませんが、pipは恐らくPythonのライブラリ検索パス上にPythonパッケージ (※) が含まれる場合に表示される作りになっています。
(※) あるルールを満たすディレクトリのこと

例えば、sudo dnf install ansibleのようにpip以外の仕組みで導入された場合も、pip listで表示されます。
そして、sudo pip uninstall ansibleを実行することで関連ファイルを削除することができます。
もしこのような方法で削除すると、おかしなことになります。

dnf list ansibleでインストール済みと表示されますが、pip listにはansibleと表示されません。
そしてansibleのコマンド実行に失敗します。
ライブラリや実行ファイルなどが削除されているため、当然です。
このような状況になったら、sudo dnf reinstall ansibleなどで再インストールして復旧できますが、混乱の度合いは計り知れません。

※dnfとpipではインストール済みか否かを判断する基準が異なると思われます。dnfは「dnf removeを実行したか否か」で判断するのに対し、pipは「パッケージの実体がファイルとして存在するか」で判断しているように見えました

このように、sudo pipは影響が大きいです。
その上、他のインストール手段と競合しうる危険性をはらんでいます。
原則として、sudo pipは使うべきではないと思います。

sudoさえつけなければ、間違えてpip uninstall ansibleを実行しても、権限不足でシステムのファイル削除は失敗してくれます。

知らぬ間にvenvの外側を破壊するケースがある

sudoをつけるとPATHの再読込が発生することで、仮想環境の外に出てしまいます。
更に、管理者権限でpip installを実行すると、--userをつけたときのような~/.local/配下ではなく、/usr/local/配下にインストールしてしまいます。

仮想環境の中であっても、sudo pip installだけは実行しないようにお気をつけください。
このコマンドを実行したら警告が出力されることからも、公式でも非推奨の操作です。

もちろん、sudo pip uninstallも原則として避けてください。
間違えてsudo pip installしてしまったパッケージを消す時だけ使うこともあるかもしれませんが...。

まとめ

sudo pipを封印すべき理由について書きました。

関連記事

endy-tech.hatenablog.jp

PythonをVS Codeで書くための環境設定

python-logo-no-wordmark

お伝えしたいこと

本記事では、VS CodePythonを動かすための環境設定を紹介します。
最低限必要なのは、以下の2点です。

上記セットアップによってできるようになることを、以下のセクションで補足します。

最後に補足として、応用的な設定を一部紹介しています。

途中でVS Codeの操作方法について記述している箇所がありますが、ショートカットキーはLinux基準となっています。
OSの違いによって読み替えが必要となりますが、そこはご了承ください。

Python拡張機能のインストール

Python拡張機能をインストールします。

Pylance拡張機能も自動的にインストールされると思います。
この拡張機能は無効化しないことを推奨します。
詳細は#Language Serverについてにて補足します。

拡張機能のインストール手順は、VS Codeの基本操作になるので割愛します。

初期設定

Python拡張機能の初期設定として、Pythonインタプリタを指定します。
/usr/bin/pythonなど、代表的なパスにある場合は自動検知してくれるので、Linuxの方は初期設定不要だと思います。

一方、後からインストールしたバージョン違いのPythonを使いたい場合は、個別に指定する必要があります。
Ctrl+Shift+P (View > Command Palette)を押してから、出てきた検索ウィンドウにPython: Select Interpreterと入力することで設定できます。

select_python_interpreter1 loading=

.pyファイルを開いている間は、左下の隅っこをクリックすることでも設定できます。

select_python_interpreter2

初期設定はこの時点で完了です。

Python拡張機能の基本機能

Python拡張機能を入れることで、追加設定不要で使える機能を紹介します。

Ctrl+F5で実行

.pyファイルを開いているときにCtrl+F5 (Run > Run without debugging)を押すだけでPythonを直ちに実行してくれます。
サンプルコードを書いてCtrl+F5を押すだけでサクサク実行できます。

学習効率に関わる部分なので、シンプルですが重要な機能です。

IntelliSense機能

VS Code のIntelliSense機能が使えるようになります (公式ドキュメント)。
IntelliSense機能は、いわゆる「コード補完」機能です。
裏でPython Language Server というコンポーネントと連携することで、以下のサービスを提供します。

機能名 機能詳細
コード補完 例えば、print()関数のprまで入力すると、入力候補を表示する
説明の表示 print()関数の基本機能、取りうる引数、返り値、型情報などを表示する

クラス、関数、プロパティ、メソッドの仕様を事細かに暗記せずとも、この機能を頼りながら効率的にプログラムを書くことができます。
学習に伴う暗記、調査のストレスを減らし、作業と学習の効率を大幅に上げてくれる必須機能です。

IntelliSense機能は、関数名などを途中まで入力してからCtrl+Spaceを押すことで呼び出すことができます。
Ctrl+Spaceを複数回押すと、コード補完の表示の詳細度を切り替わります。

以下は、listのsortメソッドの説明をIntelliSenseで表示した画像です。
Language Server はPylanceを使用しています。

completion_pylance

(参考) Language Serverについて

Language Server とは、プログラミング言語による開発をサポートするための機能を提供するサーバです。
VS Codeなどのエディタは、Language Server Protocol を介してLanguage Server と通信することで、サポート機能を享受することができます。

Python拡張機能の場合、デフォルトでPylanceというLanguage Serverが使われます。
Pylanceは上述のIntelliSense機能だけでなく、Syntax Highlighting (ソースコードの色分け) やエラーレポートなどの機能も提供します。
Pylanceは拡張機能として提供されており、Python拡張機能インストール時に依存関係として自動的に追加導入れます。
詳細はPylance拡張機能の公式ページを参照してください。

Pylance拡張機能はオフライン環境でも動作します。

実際に使ってみる

簡易なコードを例に、実際に使ってみます。

まず、Python開発用のフォルダを作っておきます (projects/python/hello_world/など)
Ctrl+K,Ctrl+O (File > Open Folder)でこのフォルダを開きます。

Ctrl+Shift+E(または左上のアイコン) からエクスプローラを開き、新規ファイルを作成します。
ファイル名は適当に、tmp.pyというファイル名にしておきます。

create_file

aptmpp.pyに以下のコードを書き、Ctrl+Sで保存します。

print('hello world')

Ctrl+F5(Run > Run without Debugging)で実行します。
すると、画面下部にターミナルが開いてPythonが実行されます。
出力が残って気になる場合は、Ctrl+Dでターミナルを閉じます (これはbashのショートカットキーです)
または、Ctrl+Shift+@ (View > Terminal)でターミナルの表示/非表示を切替できます。

Pythonの学習初期段階においてはサンプルコードを書いて動かすことの繰り返しになりますが、上記のワークフローであれば効率的に作業できるのではないかと思います。

(参考) 応用的な拡張機能

Python関連の拡張機能には、PythonやPylance以外にも色々あります。
代表的なものはLinter、Auto Formatter、Type Checkerが挙げられます。
本セクションでは、これらがどういった機能か、どうやって使うのかについてご説明します。

Linter

Linterは文法エラーやコーディング規約を満たしていない場合に直ちに警告を出し、VS Codeのエディタ上で該当箇所に赤や黄色の波線を引いて教えてくれます。
これらの警告を解決するように修正することで、ソースコードの品質をある観点で保ちやすくなります。

VS Code上でエラーの詳細を確認するには、View > Problems (Ctrl+Shift+M)でProblemsウィンドウを表示します。

以下にLintが表示するエラーのサンプルを示します。
こちらのエラーでは、無駄なimport文を実行していることと、ファイル末尾に空行が無いことで、flake8というLinterがエラーと警告をそれぞれ出しています。
PEP8などのコーディング規約を理解していなくても、ツールに教えてもらった通りに修正するだけで最低限見た目が綺麗になるのは便利ですね。
「ライトユーザーだから動けばいい」と思う方もいるかもしれませんが、むしろライトユーザーこそツールに任せて楽をするのが良いと私は思います。

lint_errors

Auto Formatter

Auto Formatterは、空行の入れ方やimport文の書き順など、明らかに動作上の影響が出ない範囲で規約に沿った書き方に自動修正してくれる機能です。
VS Code上でAuto Formatterを呼び出すには、Ctrl+Shift+Iを押下します。

例えば上記の#Linterの画面においてCtrl+Shift+Iを押下すると、importprintの間に空行2つを自動的に挿入してくれます。

Type Checker

Type Checkerは、変数の型を厳密に管理する上であると便利な機能です。
例えばある変数xに最初に1 (int型) を代入し、後からx = 'a' (str型) を代入するとType Checkerに怒られます。
私もまだまだ理解があいまいなのですが、バグを埋め込みにくいコーディングをする上で、型の管理は重要なのかなと思います。
型の理解が完全でなくとも、Type Checkerを入れておくことで良くない書き方を知るきっかけになるので、このツールもあると嬉しいものでした。

拡張機能の選び方

世の中には同じような機能を持った有名な拡張機能が複数あります。
最初はどれを使うか迷うと思いますが、行動の指針としては以下が挙げられると思います。

  1. 迷うぐらいなら何も入れない
  2. 投稿日時が新しいブログから、最もらしいことが書いてあるものを自分の感性で選び、とりあえず使ってみる。困ったら別のものに変える
  3. とりあえず複数入れてみて、自分に合ってない警告を出すLinterや、望んでいない変更を加えるAuto Formatterを後から削除する

どの方針で行くかは個々人の好みだと思います。
私は最初は1の方針だったのですが、今にして思えば2で良かったのかなと思います。

導入したツール

2021年当時、私は以下のブログを参考にしてツールを導入しました。
2021年Python開発リンター導入のベストプラクティス

そして2024年も同じツールを使い続けています。
本当に便利です。
ありがとうございました。

私の環境で導入したツールは以下の通りです。

(分類)
ツール名
機能 使い方
(Auto Formatter)
ms-python.black-formatter
空行挿入などの見た目自動調整 Ctrl+Shift+I
(Auto formatter)
ms-python.isort
import文のソート Ctrl+Shift+P ->
python.sortImports
(Linter)
nwgh.bandit
セキュリティ関連の警告表示
Complete Test Plugin Listing
ファイル保存時に自動起動
(Linter)
ms-python.flake8
PEP8準拠のコーディングスタイル指摘 ファイル保存時に自動起動
(Type Checker)
ms-python.mypy-type-checker
型チェック ファイル保存時に自動起動

(参考) RuffというLinter

#導入したツールに記述したツール群のうち、mypy以外はRuffという拡張機能でも概ね代替できます。
RuffはType Checkerではないので、Ruffを使う場合もType Checkerが必要な場合は別途インストールする必要があります (参考)。

Ruffによって以下が可能です。

  • Ctrl+Shift+IキーでほぼBlack相当のAuto Formatが可能 (参考)
  • Flake8のような指摘も可能
    • ただしRuffの指摘数はFlake8よりも少ない
    • 上記差分は「Auto Format機能で直る範囲」に収まるものが多いが、全てではない
  • isortとほぼ同じ挙動のimportソートも可能 (参考)
    • 実行方法はCtrl+Shift+P -> editor.action.organizeImports
    • 稀に異なるisortと動きをする

実際にRuffも使ってみて、私は#導入したツールにあるツール群に落ち着きました。
機能性に大きな差分は感じなかったものの、やはり私はFlake8のように細かく指摘してくれるのが嬉しかったです。

まとめ

Pythonを始めるのは面倒です。
教材探し、Pythonインストール、venvの検討、そしてIDEの検討が事前に必要です。

そこで本記事では、VS CodeによるPython開発環境の最低限のセットアップ手順を紹介しました。

本記事によって、Pythonを書き始める前の工程が少しでも短縮されれば幸いです。

Linuxで無線LANが繋がらない時の対処法

wifi-icon

お伝えしたいこと

NUC10を購入してFedoraをインストールしてから約半年間、長らく無線LANが使えない状況でした。
NUCはミニPCとはいえデスクトップなので、私は基本有線で使っています。
無線LANクライアントをずっと無効化したまま使っていました。

その状況が変わり、最近正しく動くようになったので、経緯を共有します。
無線LANが動かずに困っている方に役に立てば良いなと思います。

今回は無線LANの理解を深める前にあっさり直ってしまったため、内容としては薄めです。
1つのケーススタディとして見ていただければと思います。

環境

私の環境を貼っておきます。

項目 概要
PC本体 BXNUC10i5FNH
無線LANクライアント (内蔵) Intel® Wi-Fi 6 AX201 (参考元)
OS Fedora33

発生事象

GUIでの確認

トラブル発生時、GUI上で無線LANを有効化してもSSIDが全く表示されませんでした。

gui_wifi1 loading=

本来であれば、以下のように表示されるはずです。

gui_wifi2

CLIでの確認

CLIでも同様に、SSIDが表示されないことを確認しました。

1行目のコマンドで、無線LANクライアントが有効化されていることを確認しています。
無効の場合は、nmcli radio wifi onで有効化できます。

2行目のコマンドで、SSIDを一覧表示しようとしています。
しかし、どういうわけか何も表示されません。

nmcli radio wifi
# enabled

nmcli device wifi list
# IN-USE  BSSID  SSID  MODE  CHAN  RATE  SIGNAL  BARS  SECURITY

本来では、以下のように表示されるはずです。

nmcli device wifi list
# IN-USE  BSSID              SSID              MODE   CHAN  RATE        SIGNAL  BARS  SECURITY  
#         AA:BB:CC:DD:EE:FF  my-ssid1      Infra  11    405 Mbit/s  100     ▂▄▆█  WPA1 WPA2 
#         11:12:13:14:15:16  my-ssid2      Infra  64    405 Mbit/s  100     ▂▄▆█  WPA1 WPA2 
#         00:11:22:33:44:55  my-ssid3      Infra  9     270 Mbit/s  55      ▂▄__  WPA2 

無線LANが動かない原因

私のケースでは、2つの原因がありました。

  1. 無線LANのデバイスが正しく認識されていなかった
  2. 無線LAN暗号化通信をサポートするパッケージがインストールされていなかった

この2つの原因について、順を追って共有します。

(原因1) 無線LANバイスが正しく認識されない

必要なドライバが足りてなかったことで、無線LANバイスを正しく認識できていなかったようです。
以下に詳細なログを貼ります。

原因1の切り分け

dmesg (Linuxブート中のログ) を見ると、iwlwifiという無線LANクライアントのドライバが無線LANクライアントを正しく検出できず、何度かフォールバックしているように見えます。
※ちなみにiwlwifiは、Intel無線LANに関わるドライバの名称です (iwl = Intel Wireless LAN?)

特に、以下のエラーが悪影響を与えている印象でした。
Direct firmware load for iwlwifi-QuZ-a0-hr-b0-56.ucode failed with error -2
Direct firmware load for iwl-debug-yoyo.bin failed with error -2

しかし最後にAX201という正しいデバイスを認識できているようには見えます。
Detected Intel(R) Wi-Fi 6 AX201 160MHz, REV=0x354

dmesg | grep iwlwifi
# [    5.445530] iwlwifi 0000:00:14.3: enabling device (0000 -> 0002)
# [    5.447086] iwlwifi 0000:00:14.3: Direct firmware load for iwlwifi-QuZ-a0-hr-b0-56.ucode failed with error -2
# [    5.449460] iwlwifi 0000:00:14.3: api flags index 2 larger than supported by driver
# [    5.449471] iwlwifi 0000:00:14.3: TLV_FW_FSEQ_VERSION: FSEQ Version: 65.3.35.22
# [    5.449475] iwlwifi 0000:00:14.3: Found debug destination: EXTERNAL_DRAM
# [    5.449476] iwlwifi 0000:00:14.3: Found debug configuration: 0
# [    5.449698] iwlwifi 0000:00:14.3: loaded firmware version 55.d9698065.0 QuZ-a0-hr-b0-55.ucode op_mode iwlmvm
# [    5.449864] iwlwifi 0000:00:14.3: Direct firmware load for iwl-debug-yoyo.bin failed with error -2
# [    5.628261] iwlwifi 0000:00:14.3: Detected Intel(R) Wi-Fi 6 AX201 160MHz, REV=0x354
# [    5.806506] iwlwifi 0000:00:14.3: base HW address: 04:33:c2:81:04:11
# [    5.821843] iwlwifi 0000:00:14.3 wlp0s20f3: renamed from wlan0

ただ、この話には続きがあります。

lspciコマンドで、Linuxが認識しているPCIバイスを表示できます。
正しくはAX201と表示されるはずですが、なぜかIntel Corporation Wireless-AC 9462と表示されています。
単なる表示上の問題であれば良いのですが、iwlwifiが正しくデバイスを認識できていない可能性も出てきました。

sudo lspci -v | grep -A12 -i 'network controller'
# 00:14.3 Network controller: Intel Corporation Wireless-AC 9462
#   Subsystem: Intel Corporation Device 0074
#   Flags: bus master, fast devsel, latency 0, IRQ 16
#   Memory at 6023114000 (64-bit, non-prefetchable) [size=16K]
#   Capabilities: [c8] Power Management version 3
#   Capabilities: [d0] MSI: Enable- Count=1/1 Maskable- 64bit+
#   Capabilities: [40] Express Root Complex Integrated Endpoint, MSI 00
#   Capabilities: [80] MSI-X: Enable+ Count=16 Masked-
#   Capabilities: [100] Latency Tolerance Reporting
#   Capabilities: [164] Vendor Specific Information: ID=0010 Rev=0 Len=014 <?>
#   Kernel driver in use: iwlwifi
#   Kernel modules: iwlwifi

一方で、正常なときは、以下のようなログになります。

Linux起動時には特にエラーログはなく、フォールバックも発生していません。
読み込まれたファームウェアのファイル名も事象発生時と変わっており、QuZ-a0-hr-b0-59.ucodeと記載があります。
このファイルの実体は/usr/lib/firmware/iwlwifi-QuZ-a0-hr-b0-59.ucodeにあります。

dmesg | grep iwlwifi
# [    4.339129] iwlwifi 0000:00:14.3: enabling device (0000 -> 0002)
# [    4.341962] iwlwifi 0000:00:14.3: api flags index 2 larger than supported by driver
# [    4.341971] iwlwifi 0000:00:14.3: TLV_FW_FSEQ_VERSION: FSEQ Version: 65.3.35.22
# [    4.342152] iwlwifi 0000:00:14.3: loaded firmware version 59.601f3a66.0 QuZ-a0-hr-b0-59.ucode op_mode iwlmvm
# [    4.505557] iwlwifi 0000:00:14.3: Detected Intel(R) Wi-Fi 6 AX201 160MHz, REV=0x354
# [    4.688618] iwlwifi 0000:00:14.3: base HW address: 04:33:c2:81:04:11
# [    4.704444] iwlwifi 0000:00:14.3 wlp0s20f3: renamed from wlan0

Linuxが認識しているデバイス名も、AX201という正しい情報が表示されています。

sudo lspci -v | grep -A12 -i 'network controller'
# 00:14.3 Network controller: Intel Corporation Comet Lake PCH-LP CNVi WiFi
#   Subsystem: Intel Corporation Wi-Fi 6 AX201 160MHz
#   Flags: bus master, fast devsel, latency 0, IRQ 16, IOMMU group 5
#   Memory at 6023114000 (64-bit, non-prefetchable) [size=16K]
#   Capabilities: [c8] Power Management version 3
#   Capabilities: [d0] MSI: Enable- Count=1/1 Maskable- 64bit+
#   Capabilities: [40] Express Root Complex Integrated Endpoint, MSI 00
#   Capabilities: [80] MSI-X: Enable+ Count=16 Masked-
#   Capabilities: [100] Latency Tolerance Reporting
#   Capabilities: [164] Vendor Specific Information: ID=0010 Rev=0 Len=014 <?>
#   Kernel driver in use: iwlwifi
#   Kernel modules: iwlwifi

原因1の解決策

しばらくぶりにsudo dnf upgradeでパッケージを更新したら、上述の正常なログが出るようになりました。
ucodeファイルがそもそも足りていなかったか、iwlwifiがucodeファイルを正しく参照できていなかったか。
いずれにしても、iwlwifi自体に何らかの不具合か互換性不足があり、それがパッケージ更新によって解消されたようです。

時間が解決した、ということですね。

私の環境ではiwl7260-firmwareというrpmパッケージが関係あるようでした。
このパッケージの特定方法は、次のセクションに書きます。

(参考) 必要なrpmファイルの特定の仕方

iwlwifiのドライバが含まれるrpmパッケージは、以下のとおりたくさんあります。
ここでは、以下のrpmパッケージの中に自分が使っている無線LANクライアントに対応したものがあるか確認する方法を紹介します。
今回の方法はrpmを利用するRedHat系かつ、Intel無線LANクライアントを使っている場合のみ使えます。
条件に該当しない方は、適宜手順をアレンジする必要があります。

rpm -qa | grep iwl
# iwl100-firmware-39.31.5.1-119.fc33.noarch
# iwl1000-firmware-39.31.5.1-119.fc33.noarch
# iwl105-firmware-18.168.6.1-119.fc33.noarch
# iwl135-firmware-18.168.6.1-119.fc33.noarch
# iwl2000-firmware-18.168.6.1-119.fc33.noarch
# iwl2030-firmware-18.168.6.1-119.fc33.noarch
# iwl3160-firmware-25.30.13.0-119.fc33.noarch
# iwl3945-firmware-15.32.2.9-119.fc33.noarch
# iwl4965-firmware-228.61.2.24-119.fc33.noarch
# iwl5000-firmware-8.83.5.1_1-119.fc33.noarch
# iwl5150-firmware-8.24.2.2-119.fc33.noarch
# iwl6000-firmware-9.221.4.1-119.fc33.noarch
# iwl6000g2a-firmware-18.168.6.1-119.fc33.noarch
# iwl6000g2b-firmware-18.168.6.1-119.fc33.noarch
# iwl6050-firmware-41.28.5.1-119.fc33.noarch
# iwl7260-firmware-25.30.13.0-119.fc33.noarch

まず、自分が利用している製品の型番を前述のlspci -vで確認します。
lspci -vmmの方が見やすいかもしれません。
今回の場合は、AX201だとわかったとします。

この後すぐできる最も簡単な確認方法は、dnf searchコマンドで検索することです。
パッケージの説明文に型番が書いてあるので、今回はこの方法が使えます。
この結果、iwl7260-firmwareがヒットしました。

dnf search AX201
# iwl7260-firmware.noarch : Firmware for Intel(R) Wireless WiFi Link 726x/8000/9000/AX200/AX201 Series Adapters

上記方法でヒットしないケースもあります。
例えばAX210に対応するドライバも上記パッケージに本当は含まれているのですが、説明文に書いてないのでヒットしません。
そんなときは、次の方法で確認します。

Linux Wireless WikiでサポートされているIntel無線LAN機器の一覧を確認します。
このページでAX201を検索し、対応するドライバが含まれるtar.gzファイルをダウンロードして中身を確認します。
今回は、iwlwifi-Qu-48.13675109.0.tgzをダウンロードします。

すると、中にいくつかのucodeファイルが入っています。
どれでも良いので、一つのucodeファイルの名前をメモしてください。
今回は、iwlwifi-Qu-b0-hr-b0-48.ucodeにします。

続いて、dnf providesコマンドで上記ファイルを含むrpmパッケージを検索します。
dnf providesには、ファイルをフルパスで指定する必要があります。
今回は/usr/lib/firmware/iwlwifi-Qu-b0-hr-b0-48.ucodeが正しい指定方法なのですが、フルパスを知らなくてもワイルドカードで検索できます。
**は、任意の文字列かつ、任意階層分だけフォルダを下った場合もマッチするワイルドカードです。
言い換えると、**は「/を含む任意の文字列」にマッチします。

dnf provides **iwlwifi-Qu-b0-hr-b0-48.ucode
# iwl7260-firmware-1:25.30.13.0-119.fc33.noarch : Firmware for Intel(R) Wireless WiFi Link 726x/8000/9000/AX200/AX201 Series Adapters

以上の確認により、AX201に必要なrpmパッケージはiwl7260-firmwareだとわかりました。

もちろん、私の環境において必要だったQuZ-a0-hr-b0-59.ucodeも今回の検索でヒットします。

dnf provides **QuZ-a0-hr-b0-59.ucode
# iwl7260-firmware-1:25.30.13.0-119.fc33.noarch : Firmware for Intel(R) Wireless WiFi Link 726x/8000/9000/AX200/AX201 Series Adapters

本来必要なucodeファイルは、事象解消時のdmesgから読み取れます。
トラブル発生中に必要なucodeファイルを見抜く方法は、残念ながら私にはわかりませんでした。

# [    4.342152] iwlwifi 0000:00:14.3: loaded firmware version 59.601f3a66.0 QuZ-a0-hr-b0-59.ucode op_mode iwlmvm

(原因2) パッケージが足りていない

原因1を解決してもSSIDが表示されなかったので、更に調査を進めました。
その結果、wpa_supplicantというパッケージが不足していることが原因だとわかりました。

原因2の切り分け

無線LANを有効化したタイミングで、/var/log/messagesに以下のログが発生していました。
恐らく、journalctl -xeu NetworkManagerでも同様のログを確認できると思います。

NetworkManager[950]: <error> [1619738834.7337] device (wlp0s20f3): Couldn't initialize supplicant interface: Failed to D-Bus activate wpa_supplicant service

原因2の解決策

以下のコマンドでwpa_supplicantをインストールすることで直りました。

sudo dnf install wpa_supplicant

このパッケージにより、wpa_supplicant.serviceが作成されます。
このサービスは無線LANを有効化したタイミングで自動起動されるので、有効化する必要はないようです。
sudo systemctl enable --now wpa_supplicant.serviceの実行は、私の環境では不要でした。

以上の対応により原因1と原因2を解消し、無事無線が使えるようになりました。

(参考) wpa_supplicantとは?

wpa_supplicantとは、WEP、WPA、WPA2などの無線LAN暗号化技術における認証クライアントです。
言い換えれば、上述の暗号化技術を用いて無線LAN接続をするのに必要なプログラムです (参考:ArchLinux Wiki)。

初期状態のFedora33には、wpa_supplicantがインストールされていないようです。
これにより、SSIDを表示することすらできなかったようです。

(参考) 起動できないネットワークは無効化すべき

主張

今回の事象のように、無線LANが使えないなどの理由でNetworkManagerのconnectionが起動できないケースはあると思います。
基本的には、connectionが使えないときはその原因を排除して使えるようにするのが良いと思います。
しかしどうしても解決できないときは、OS起動の度にconnection起動を失敗させるのではなく、connectionを初めからdownさせておくべきです。
コマンドでいうと、nmcli radio wifi offなどです。
autoconnect offよりも更にシンプルに、デバイスレベルでwifiを無効にします。

理由

起動に失敗するconnectionを停止しておくべき理由は、OSの起動が遅くなるためです。

LinuxのOSが起動する際、様々なサービスが順番に起動します。
systemctl cat <unit>を実行するとわかりますが、サービス間には依存関係があります。
特に多くのサービスは、ネットワークに依存しています。

つまり、ネットワーク接続が完了しないと、他のサービスの起動が開始されません。

NetworkManager.service については、ネットワーク起動を待つための専用サービスがあります。
それがNetworkManager-wait-online.serviceです。
中身を見てみましょう。
description、実行コマンド、環境変数のみ抜粋しています。

systemctl cat NetworkManager-wait-online
# `nm-online -s` waits until the point when NetworkManager logs
# "startup complete". That is when startup actions are settled and
# devices and profiles reached a conclusive activated or deactivated
# state.

# ExecStart=/usr/bin/nm-online -s -q

# Environment=NM_ONLINE_TIMEOUT=30

内部的にはnm-online -s -qを実行していて、NetworkManagerがstartup completeというログを出力するまで待つようです。
NetworkManagerが30秒以内にログを出さなければ、NetworkManager-wait-onlineタイムアウトする作りです。
NetworkManager-wait-onlineは、NetworkManagerサービスの起動を最大30秒待つことで、他のネットワークに依存するサービスの起動失敗を防ぐ仕組みです。

内部的に実行しているnm-onlineのmanを見てみます。
-sオプションの説明に以下の記述がありました。
autoconnect (connectionの自動起動) に失敗すると、NetworkManagerの起動完了を遅延させるケースがあるようです。
実際、無線LAN自動起動に失敗することでLinuxの起動が遅くなることが確認できました。

For example, by setting a connection profile to autoconnect, such a profile possibly will activate during startup and thus delay startup complete being reached.

どの程度起動が遅くなるのか

では、connection自動起動の失敗により、Linuxの起動がどの程度遅くなるのか。 systemd-analyze blameコマンドで大体の検討がつきます。

systemd-analyze blameを実行すると、各サービスの起動にかかった時間を長い順に表示します。
NetworkManager-wait-onlineの数字が30秒の場合は、何かがおかしいことが多いです。

systemd-analyze blame
# 10.568s dnf-makecache.service
#  9.205s NetworkManager-wait-online.service
#  948ms systemd-udev-settle.service

ただし気をつけていただきたいのは、この表示はLinux起動時間に影響を与えた時間ではありません。
純粋に各プロセスの起動から停止までにかかった時間を表しています。
上記表示は、「NetworkManager-wait-online.serviceによりLinuxの起動が9.205秒遅延した」という意味ではありません。
...今回の場合は、起動に約9秒の影響を与えていますが。

影響を見るには、次のコマンドが適切です。
起動時間のグラフをSVGファイルとして出力します。
GUIで開くことで、画像として開くことができます。

systemd-analyze plot > tmp.svg

グラフは以下のように見えます。
NetworkManager-wait-online.serviceが完了するまで9.2秒待ち続け、その後network-online.targetが起動し、他のネットワークに依存するサービスが起動開始しています。

systemd-analyze

まとめ

Linux無線LANが動作しない原因を2パターン紹介しました。
同じような事象で困っている方の役に立てれば嬉しいです。

無線LANがうまく動作しないときは停止させておくのもぜひ実践してみてください。
Linuxの起動が更に早くなりますよ。
一方、NetworkManager-wait-onlineを無効化するのは、やめておいたほうが良いと思います。

関連記事

Linux パソコン関連の記事をまとめてあります。

endy-tech.hatenablog.jp

メインPCをLinuxにして良かったこと、困ったこと

20200927134745

お伝えしたいこと

メインPCをWindowsMacではなく、Linuxにしてから半年以上経ちました。

実際のところ使っていてどうなのか、思ったことを好き勝手に書きます。
よろしければ、気軽に拾い読みしてください。

私は元々Windowsユーザーで、Macには全く詳しくありません。
従って、WindowsLinuxの対比が多くなります。

また、Linuxが好きになったので、Linuxをひいきしたコメントが目立ちます。
そのあたりはご了承ください。

環境

メインPCの環境を書きます。

まずはハードウェアスペックです。
CPU控えめ、メモリ多め、グラボ無しです。
後は読み飛ばして結構です。

項目 詳細
PC本体 デスクトップ
BXNUC10i5FNH
CPU Intel Core i5-10210U
モバイル用、4コア、最大4.20GHz
RAM 64 GiB
M471A4G43MB1 (32GB, DDR4) x2枚
SSD 1 TiB, M.2 NVMe
WDS100T2B0C

OSとデスクトップ環境は以下のとおりです。

項目 詳細
OS Fedora 33
デスクトップ環境 Cinnamon

Linuxにしてよかったこと

コスパ

Windows 10 は1万7千円ぐらいします。

一方で、Linuxは無料です。

余ったお金をCPU/メモリに充てられます。

検証環境を整えやすい

Windowsで検証をする時、あれこれ工夫が必要で、一筋縄に環境構築できません。
環境構築した後も、たまにソフトウェアを更新しようと思った時に「当時の工夫」を思い出しつつ対応する必要があり、これに中々労力がかかります。
特に心労が...本当に...凄まじいです。

一方で、Linuxの環境構築はWindowsと比べてシンプルかつ簡単です。
パッケージマネージャーでソフトを入れたらおしまい...のことが多いです。
もちろんOSのパッケージマネージャ (dnf, apt) によって手順は異なりますが、ググればすぐに情報が出てきます。
それに、こういったことを調べること自体がLinuxの良い勉強になるところが良い点ですね。

MacWindowsより扱いやすいと思いますが、やはりOSのセキュリティ機能や細かな仕様の差によって苦しむ場面がある印象です。

例1. Python

Linuxだと初めからPython3が入っています。

WindowsはPython3を後からインストールする必要があります。
最近はMicrosoft StoreのPython3が初めから入っていて、これが中々曲者でした。
後からインストールしたPythonよりもMicrosoft StoreのPythonの方がPATHの優先順位が高い。
更に、Microsoft Store版のPythonには不具合があった...と記憶しています。
とにかく面倒でした。。

例2. Ansible

Ansibleに限らず、KVMなどLinuxでしか動作しないソフトには概ね該当すると思います。

LinuxはvenvにAnsibleを入れてすぐ動かせます。
3行のコマンドでセットアップできます。

python3 -m venv ~/venv/ansible
source ~/venv/ansible/bin/activate
pip install ansible

WindowsではAnsibleが動かないので、Linux環境を用意する必要があります。
Linux VMを作るか、WSLを導入するかですね。
その上で、ゲストLinux上で同じ工程を踏む必要があります。
これが中々の手間かなと思います。

コマンドラインが便利

Linuxなら、当然ながらgrepsedawkなどの便利コマンドを色々使えます。

Windowsでもcmdやpowershellに慣れている方はあまり困らないかもしれませんね。
あるいはWSL・Git for Windowsの環境を整えている場合も、それほど不便は感じないかもしれません。

それでも私はcmderやWindows Terminal (WSL) よりも、Linuxターミナルの方が好きです。

ターミナルソフトが充実している

Macも同様かもしれませんが。

Teratermに負けないぐらい、便利なターミナルがたくさんあります。
私のお気に入りはTilixです (過去記事)。
多機能で不具合がなく、ショートカットキーも自由にカスタマイズできるためです。

ソフトウェアのインストールが楽

Linuxならコマンド一発でパッケージインストールできます。

WindowsインストーラをWEBから探してきて、ウィザードをポチポチする必要があります。
ただ、chocolateyなどのツールもあるようなので、このあたりはあまり関係ないのかも...とも思います。

改行コード、文字コード

Linuxにしてから、\r\nShift-JISに悩まされることはなくなりました。

動作が早い

起動時間についてはあまり変わらないかもしれません。
Linuxはデスクトップ環境を動かしていても15秒ほどで起動します。
※もちろん、SSDを搭載していることも大きいです

ただし、Windowsは起動完了となった後も裏でTelemetryやExplolerのインデックス作成、Windows Updateの受信などでディスクI/Oが絶え間なく発生していました。
一方でLinuxに変えてからはそういったことはなく、無駄なディスクI/Oはほぼなくなりました。

余談ですが、Windowsについても、debloat windowsなどのキーワードで検索すると、バックグラウンドプロセスを一括で停止するようなスクリプトを配布しているサイトもあります。
実行は自己責任ですが、確かに多少は早くなります。
Windows Updateで停止したプロセスが復活するので、都度再実行が必要です。
★参考

ちなみに、VirtualBoxやESXiにLinuxデスクトップ環境を入れて動かすと、非常にもっさりしていると思います。
それは仮想環境で動かしているためです。
WindowsVMでも同様に重くなります。
Linuxを物理マシンに直接インストールすれば、とても快適に動作します。

OS更新

Linuxには、Windowsのような強制バージョンアップや強制再起動はありません。
また、再起動ループのようなひどいバグも今のところ引いていません。

もちろん、Linuxでもサポート切れにならない程度に年1程度のアップグレードは必要です。
Fedoraについては、3行程度のコマンドを入れるだけで、トラブルなくアップグレードできました (過去記事)。

レジストリがない

Windowsレジストリって難しくないですか?
私だけでしょうか。

Linuxにはレジストリがないのがありがたいです。

Linuxにして困ったこと

周辺機器の動作

Windowsでは周辺機器を認識しないことで困ったことはあまりないと思います。

一方、私のLinux環境では、イヤホンが認識されなかったり無線LANが使えなかったりしました。
ドライバ関連で問題が出るケースはあります。
どちらも私の環境では解決できましたが、環境次第では解決に時間がかかることもあると思います。

もし解決できなければ、イヤホンが認識されない場合はBluetoothスピーカーで我慢する。
無線LANが使えない場合は有線で我慢する。
...といった回避方法は環境次第で取れると思います。

また、パソコンでゲームしたい方は、グラフィックカードのドライバがLinuxでも問題なく動くか確認したほうが良いかもしれません。

初期設定の難易度

Linuxの初期構築時、Linuxの知識は必要になります。

Linuxのインストールに始まり、デスクトップ環境を導入したり、日本語環境を整えたり...といった具合です。

これらのタスクは面倒ですが、良い経験値になります。
検証環境の踏み台サーバを作ったり、ネットワーク検証用のsyslogサーバを建てたり...。
インフラエンジニアであれば、必ず役に立つノウハウが得られます。

Office

LinuxではMS Officeが動きません。
代替製品を使うことになります。

ExcelPowerpointを日常的に使う方には辛いかもしれません。

私は私生活でOfficeを使う機会が限られているので、ここは妥協しました。
Google SpreadSheetやGoogle Slidesの使い勝手が十分良いので、それほど困りませんでした。

ただクレジットカードやら家計簿など、どうしてもクラウドに置きたくないファイルにはGoogle Appsは使えません。
オフライン環境が必要な場合に限り、FreeOfficeで妥協しています。
使い勝手は60点ぐらい...ですが、機会が少ないので我慢しています。

フリーソフト

Windowsで使えていたフリーソフトも、Linuxには存在しないケースがあります。
ただ、探せば結構見つかります。

以下に代替ソフトの例を書きます。

Windows Linux 備考
ペイント Pinta 軽量なお絵描きツール
Snipping Tool Ctrl + Shift + PrtScreen ソフト不要
メディアプレイヤー Celluloid ショートカットキー充実
軽量
オフィス GSSなど
FreeOffice
OnlyOffice
WPS Office
完璧なものはない

裏を返せば、上記以外でソフトがなくて困ったことはあまりありません。

フォント

LinuxではMSゴシックなどのフォントが使えません。
MS系のフォントはその名の通りMicroSoftの製品に付属するもので、フリーフォントではないためです。

私生活ではあまりないかもしれませんが、Excelなどのローカルのファイルを他の人と共有するときに、互換性に難儀するかもしれません。
ただ家のパソコンで仕事することが無いのであれば、困ることはそうそうないと思います。

自分の作業の都合だけであれば、Noto Sans CJK JPSource Code Proなどを使っておけば生活に困ることはありません。

(おまけ) Fedoraを選んだ理由

RedHatディストリビューションの操作に慣れていたためです。
また、CentOSなどと比較してパッケージの品揃えが圧倒的に良いです。
新しいパッケージの方が使い勝手がよく、互換性もあり、ちゃんと動きます。
パソコン用途ならCentOSよりFedoraの方が良いですよ。

後から海外エンジニアの動画などで何度も聞いたのですが、RedHat系のディストリビューションはよくテストされていて、安定性に優れているとのことです。
たまたまですが、そういった意味でも良い選択でした。

ただ、debの方がrpmと比較してパッケージの品揃えが良いようです。
パソコンでゲームをするなど、検証以外でも多様な用途でパソコンをフル活用したい場合は、Debian系のディストリビューションもありかもしれません。
Linux Mintなど、扱いやすくて人気がありますよね。

(おまけ) Cinnamon を選んだ理由

Windowsから移行して全く違和感がないのがCinnamonでした。
それどころか、Windowsよりデザインや使い勝手が良く、とても気に入っています。
他にもWindowsライクなデスクトップとしてKDEがありますが、設定画面のわかりやすさではCinnamonに軍配が上がりました。

拡張機能 (Desklet) の充実具合は圧倒的にKDEですが。
Cinnamonの場合は拡張機能を一切使わないことになると思います。

他にも以下のような特徴があります。

  • 動作が安定している
  • UIに統一感があってわかりやすい
  • Windowsライクな見た目
  • Windowsライクなショートカットキー (Alt+F4など)。更にカスタマイズも可能
  • 普通に綺麗
  • Windowsより良いところも...
    • File Manager (≒Explorer) にデフォルトでタブがついている
    • Windowsにはないショートカットキーもある
    • ショートカットキーを手軽に設定できる

Mac出身の方は別のデスクトップ環境の方が良いのかもしれません。

最後は結局好みになるので、仮想マシン上に様々なデスクトップ環境をインストールして、あれこれ試す工程は必ず必要になると思います。

まとめ

LinuxをメインPCにして感じたことをあれこれ書きました。
まとめると、「はやい、やすい、うまい簡単」に尽きます。

ゲーミングPCとしての活用は難しそうですが、検証やブラウジングやドキュメント作成に限れば非常に使い勝手が良いです。

Windowsが重くてうまく動かないノートPCなどあれば、試しにLinuxを入れてみてはいかがでしょう?
思いがけず、良いサブPCが手に入るかもしれません。

venvが動作する仕組みを調べてみた

python-logo

お伝えしたいこと

Pythonのvenvがどのような仕組みで動いているか気になったので、少し詳しく調べてみました。
ソースコードは読めないので、いつものようにドキュメント上の仕様を追いかけつつ、実機で検証してみました。

今回は、「興味のある方向け」のマニアックな記事です。
あまり興味のない方は、サマリのみご覧ください。

前提知識として、venvの基本的な使い方の理解が必要です。

サマリ

まずは要点を書きます。
これだけで十分かもしれません。

仮想環境ディレクトリ配下の実行ファイル (仮想環境/bin/*) を実行すると、以下の動きになります。

source 仮想環境/bin/activateを実行すると、PATHの先頭に仮想環境/binが追加されます。
これによりパスを指定せずにコマンド実行すると、/usr/binなどのシステム全体の実行ファイルよりも仮想環境の実行ファイルが優先的に実行されます。
例えば仮想環境内でansibleコマンドを実行すると、/usr/bin/ansibleよりも仮想環境/bin/ansibleが優先的に実行されます。
そして仮想環境配下の実行ファイルを実行すると、上述の通り仮想環境と紐づくPythonインタプリタとライブラリが参照されるので、諸々が仮想環境配下で動くようになります。

上記の動作を実装しているのがsite.pyというスクリプトであり、その中でも重要な役割を果たすのがsys.executablesys.prefixといった値です。
以降のセクションでより詳細な仕組みを紹介しますので、興味のある方はご覧ください。

PATHの変更

ドキュメント確認

Python仮想環境に入る時、source 仮想環境/bin/activateコマンドを実行します。
ここで実行しているactivateは仮想環境作成時に自動生成するシェルスクリプトで、内部的には以下の処理を実行しています。

  • PATHの先頭に仮想環境配下のファイル (仮想環境/bin) を追記する
    • 例えば仮想環境に入ったあとにpipコマンドを実行すると、/usr/bin/pipの代わりに仮想環境/bin/pipが呼ばれるようになる
  • 他にも以下の処理をしている
    • 環境変数VIRTUAL_ENVに仮想環境のディレクトリパスを格納
    • 環境変数PS1を書き換えることで、シェルプロンプトの文字列を変更
    • 仮想環境から抜けるためのdeactivateコマンドを定義

実機確認

前提として、予め~/venv/systemにsystemという名前のPython仮想環境を作成しておきます。

$ python3 -m venv ~/venv/system

仮想環境に入る前後でPATHを比較してみます。
仮想環境に入った後は、PATHの先頭に~/venv/system/binが追加されていました。

$ echo $PATH
/home/endy/.local/bin:/home/endy/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

$ source ~/venv/system/bin/activate

(system)$ echo $PATH
/home/endy/venv/system/bin:/home/endy/.local/bin:/home/endy/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin

では、~/venv/system/bin配下のファイルがどうなっているかというと、Shebangで仮想環境配下のPythonインタプリタを指定していました。
そして、仮想環境配下のPythonは、venvを作成した時のPythonへのシンボリックリンクになっていました。

$ head -1 ~/venv/system/bin/pip
#!/home/stopendy/venv/system/bin/python

$ file /home/stopendy/venv/system/bin/python
/home/stopendy/venv/system/bin/python: symbolic link to /usr/bin/python

結局のところは、シンボリックリンク越しに/usr/bin/pythonが呼ばれる作りになっています。

そうなると「仮想環境のPythonを実行しても、結局/usr/bin/pythonシンボリックリンクされているなら同じ動作になるじゃないか」と思いたくなるところですが、実は「一旦仮想環境配下のPythonインタプリタのパスを指定して実行することで、仮想環境を利用している扱いになる」ような仕組みがsite.pyに実装されています。
具体的には、sys.executableに直接実行されたPythonインタプリタシンボリックリンクのパス (仮想環境/bin/python) が格納され、それによって「venv内である」と識別しています。

次のセクションに詳細を書きます。

Pythonの実行パスによってprefixを書き換える

ドキュメント確認

sys.prefix

前提知識として、sys.prefixsys.exec_prefixの仕様を紹介します。

sys.prefixsys.exec_prefixには、Pythonのインストール先のパス (./configure --prefix=...) がセットされています。
多くの場合、Pythonソースコードからビルドしてインストールした場合は/usr/localPythonrpmなどのパッケージからインストールした場合は/usrがセットされます。

sys.prefixsys.exec_prefixの値は、Pythonスクリプトを実行する度に後述のsite.pyによって書き換えられます。
一方でsys.base_prefixsys.base_exec_prefix (base_prefixと呼びます) も同じデフォルト値 (/usrなど) を持ちますが、site.pyによって書き換えられることはなく常に同じ値を持ち続けます。

site.pyにおいて上記4種類のprefixがどのような意味を持つかは、次のセクションで詳しく説明します。

site.py

sys.pathは、Pythonパッケージの検索パスを示す値です。
そして、sys.pathの値をセットすることで、Pythonパッケージの検索パスを決めているのがsiteです (ソースコード)。
site.pyの処理の中で、Pythonの実行パスから仮想環境を判定し、sys.pathの値を書き換える処理も書いてあります。
つまり、site.pyの動作を理解することがvenvを理解することに繋がります。

site.pyのコメント部分を読むと、以下の処理をしていると記載がありました。
1つ目の処理は前準備で、2つ目の処理が最も重要です。

  1. sys.executable(実行したPythonインタプリタへのファイルパス。仮想環境の場合はシンボリックリンクへのファイルパス) の1つ上のディレクトリにpyvenv.cfgが存在した場合、そのディレクトリのパスがsys.prefixsys.exec_prefixにセットされる
  2. Unix系OSの場合は、sys.prefixsys.exec_prefixの末尾にlib/pythonX.Y/site-packagesを追記したパスをsys.path配列に追加する
  3. pyvenv.cfginclude-system-site-packages = false以外の値が指定されていた場合は、sys.base_prefixsys.base_exec_prefixで示されるシステムのPythonパッケージも検索対象としてsys.pathの末尾に加わる

実機確認

机上で確認したvenvの仕様を実機上でも確認してみます。

仮想環境に入る前

prefixは、全て/usrを指しています。
すなわち、Pythonパッケージの検索パスは/usr/lib/python3.9/site-packages/配下になります。
このことは、sys.pathの値からも読み取れます。

$ python3 -c 'import sys; print(sys.executable)'
/usr/bin/python3'

$ python3 -c 'import sys; print(sys.base_prefix)'
/usr

$ python3 -c 'import sys; print(sys.base_exec_prefix)'
/usr

$ python3 -c 'import sys; print(sys.prefix)'
/usr

$ python3 -c 'import sys; print(sys.exec_prefix)'
/usr

$ python3 -c 'import sys; print(sys.path)'
['', '/usr/lib64/python39.zip', '/usr/lib64/python3.9', '/usr/lib64/python3.9/lib-dynload', '/usr/lib64/python3.9/site-packages', '/usr/lib/python3.9/site-packages']

仮想環境に入った後

まずは、pyvenv.cfgと、仮想環境内のPython実行ファイルの位置関係を確認してみます。
仮想環境を作った時点で、~/venv/system/配下にpyvenv.cfgpythonコマンドへのシンボリックリンクが生成しています。
python実行ファイルの上の階層にpyvenv.cfgが存在する」というPython仮想環境として動作するための条件を満たしています。
つまり、仮想環境配下のPythonを実行すると、sys.prefixsys.exec_prefixが書き換わることになります。

(system)$ file ~/venv/system/pyvenv.cfg ~/venv/system/bin/python3
/home/endy/venv/system/pyvenv.cfg: ASCII text
/home/endy/venv/system/bin/python3: symbolic link to /usr/bin/python3

次に、sys.pathが仮想環境に入ることで変わる様子を確認します。
sys.prefixsys.exec_prefixは仮想環境を反映して/home/endy/venv/systemに変わっています。
これに伴って、Pythonパッケージの検索パス (sys.path) も仮想環境配下に切り替わっています。

一方で、sys.base_prefixsys.base_exec_prefixは特に値が書き換わることのない仕様のため、変わらず/usrにセットされたままです。
これによって仮想環境と実環境のPythonパッケージパスの両方を区別しています。
pyvenv.cfginclude-system-site-packages=trueオプションを使う場合、こういった区別が必要になります。

$ source ~/venv/system/bin/activate

(system)$ python3 -c 'import sys; print(sys.exec_prefix)'
/home/endy/venv/system

(system)$ python3 -c 'import sys; print(sys.executable)'
/home/endy/venv/system/bin/python

(system)$ python3 -c 'import sys; print(sys.base_prefix)'
/usr

(system)$ python3 -c 'import sys; print(sys.base_exec_prefix)'
/usr

(system)$ python3 -c 'import sys; print(sys.prefix)'
/home/endy/venv/system

(system)$ python3 -c 'import sys; print(sys.path)'
['', '/usr/lib64/python39.zip', '/usr/lib64/python3.9', '/usr/lib64/python3.9/lib-dynload', '/home/stopendy/venv/system/lib64/python3.9/site-packages', '/home/stopendy/venv/system/lib/python3.9/site-packages']

更に、仮想環境のpyvenv.cfgについて調べてみましょう。

まず、pyvenv.cfgの中身を見てみます。
include-system-site-packages = falseがデフォルトで記載されているので、この部分を変更しない限りは仮想環境にシステムのパッケージを読み込まないことになります。

(system)$ file ~/venv/system/bin/python3
/home/endy/venv/system/bin/python3: symbolic link to /usr/bin/python3

(system)$ cat ~/venv/system/pyvenv.cfg 
home = /usr/bin
include-system-site-packages = false
version = 3.9.0

pyvenv.cfgのファイル名を変えてみたら仮想環境が認識されなくなり、sys.prefix/usrを指すようになりました。
自動生成しているpyvenv.cfgは、仮想環境の肝なので誤って削除しないよう気をつけましょう。

(system)$ mv ~/venv/system/pyvenv.cfg ~/venv/system/_pyvenv.cfg
(system)$ python3 -c 'import sys; print(sys.prefix)'
/usr

まとめ

Pythonの根幹にはsite.pyが存在し、Python起動時のコマンドラインのパスによってPythonパッケージ検索先 (sys.path) を書き換える仕組みを実装していました。
Python実行ファイル (シンボリックリンク) の一階層上にpyvenv.cfgファイルが存在すること」がsys.pathを書き換える条件です。

venvによって仮想環境を生成すると、これらの条件を満たすようにpyvenv.cfgpython実行ファイルへのシンボリックリンクを生成します。
そして、source 仮想環境/bin/activateを実行すると、PATHが書き換わることで仮想環境のシンボリックリンクを参照するようになります。
これによってsys.executablesys.prefixsys.exec_prefixsys.pathなどが書き換わり、Pythonimportした時のモジュール検索パスが変わっているという原理でした。
本記事では触れていませんが、pip install実行時のインストール先のディレクトリも上記で書き換わった変数を参照しています。

余談ですが、「仮想環境に入る」という操作をPATHの書き換えによって実現していたということは...やはりsudo pipはPATHを書き換えるのでご法度です。
sudoを実行することで、知らず知らずの間に仮想環境の外に出てしまいます。
恐ろしいですね。。

さて、前の記事と併せて、venvの使い方と仕組みについて理解が深まりました。
私自身も含めてですが、これからPythonプログラミングやPython関連ツールの利用を始める方にとってお役に立てれば幸いです。