hana_shinのLinux技術ブログ

Linuxの技術情報を掲載しています。特にネットワークをメインに掲載していきます。

Ansibleの使い方(モジュール編)

1 はじめに

本記事では、Ansibleのモジュールの使い方について説明します。モジュールとは、ターゲットノード(リモートホスト)で実行される操作を指します。モジュールを使用すると、ターゲットノード上でファイルの作成や削除などを行うことができます。なお、モジュールの一覧は以下を参照してください。
Ansible.Builtin — Ansible Core Documentation

2 検証環境

モジュールの動作検証をするための環境は以下のとおりです。

                 192.168.122.0/24
control ------------------------------------- node1
      .220                               .87
ホスト名 役割
control コントロールノードとして動作します。Ansibleをインストールします。
node1 ターゲットノードとして動作します。モジュールが実行されるホストです

コントロールノード、ターゲットノードのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

コントロールノード、ターゲットノードのカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

3 事前準備

3.1 Ansibleパッケージのインストール

Ansibleのパッケージはepel-releaseリポジトリにあるので、まずepel-releaseパッケージをインストールします。

[root@control ~]# dnf install epel-release

Ansibleパッケージをインストールします。

[root@control ~]# dnf install ansible

Ansibleの版数を確認します。

[root@control ~]# ansible --version
ansible [core 2.14.9]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.16 (main, Dec  8 2022, 00:00:00) [GCC 11.3.1 20221121 (Red Hat 11.3.1-4)] (/usr/bin/python3)
  jinja version = 3.1.2
  libyaml = True

3.2 公開鍵認証方法のセットアップ

Playbookを実行する際、コントロールノードからターゲットノードにSSHでログインします。ここでは、パスワード認証ではなく公開鍵認証を使用してログインします。以下では、公開鍵認証の設定方法を説明します。

コントロールノードで鍵ペアを作成します。秘密鍵が漏洩しても、秘密鍵が悪用されないようにするため、秘密鍵パスフレーズを設定します。なお、ssh-keygenコマンドの使い方は、ssh-keygenコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@control ~]# ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/root/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /root/.ssh/id_ed25519
Your public key has been saved in /root/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:CJCKkLdQN2qaLjyGsJEqp2LwVL7DI5148sKdnLq2yPo root@control
The key's randomart image is:
+--[ED25519 256]--+
| oo.o            |
|+ o+ .           |
|o+o..            |
|o=. .. .         |
|*  o  . S        |
|Bo. .            |
|BO.* =           |
|*=X &            |
|B+EX.o           |
+----[SHA256]-----+

ssh-keygenコマンドで作成した鍵ペアを確認します。公開鍵がid_ed25519.pub、秘密鍵がid_ed25519となります。

[root@control ~]# ls -l .ssh/id_ed25519*
-rw-------. 1 root root 444  2月 17 21:44 .ssh/id_ed25519
-rw-r--r--. 1 root root  94  2月 17 21:44 .ssh/id_ed25519.pub

ssh-copy-idコマンドを使って、公開鍵をターゲットノードの/root/.ssh/authorized_keysに追加します。

[root@control ~]# ssh-copy-id -i /root/.ssh/id_ed25519.pub root@192.168.122.220
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_ed25519.pub"
The authenticity of host '192.168.122.220 (192.168.122.220)' can't be established.
ED25519 key fingerprint is SHA256:qOFHEFGjbm/592tXQ75TCKUxXfHwTFx+wZisx5LN2uM.
This key is not known by any other names
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.122.220's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@192.168.122.220'"
and check to make sure that only the key(s) you wanted were added.

authorized_keysファイルを確認します。公開鍵がauthorized_keysファイルに追記されたことがわかります。

[root@node1 ~]# cat .ssh/authorized_keys
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFlLtp0lJeI2sU28wTlCADMz4ol7iJZLNM0eciAS03dF root@control

3.3 パスフレーズ入力の省略

ターゲットノードにsshログインするたびにパスフレーズを入力するのは手間がかかります。そのため、SSHエージェントを使用して初回のみパスフレーズを入力し、その後はパスフレーズの入力を省略できるように設定します。

SSHエージェントを起動します。

[root@control ~]# eval "$(ssh-agent -s)"
Agent pid 1934

SSHエージェントが保持している秘密鍵のリストに新しい秘密鍵を追加します。パスフレーズを入力すると、これ以降のsshログインはパスワードなしでログインできるようになります。

[root@control ~]# ssh-add /root/.ssh/id_ed25519
Enter passphrase for /root/.ssh/id_ed25519:
Identity added: /root/.ssh/id_ed25519 (root@control)

コントロールノードにsshログインします。パスフレーズ入力せずに、ログインできることがわかります。

[root@control ~]# ssh 192.168.122.220
Last login: Thu Feb 22 21:11:54 2024 from 192.168.122.87

4 インベントリファイルの作成

管理対象となるサーバの一覧を記述したインベントリファイルを作成します。この後のモジュールの動作確認でも、このインベントリファイルを使用します。node1 はターゲットノードのホスト名であり、ansible_host はターゲットノードのIPアドレスを定義しています。

[root@control ~]# vi hosts.ini
[root@control ~]# cat hosts.ini
[nodes]
node1 ansible_host=192.168.122.220

5 fileモジュールの使い方

fileは、ファイルの作成、削除等を行うモジュールです。詳細は、ansible.builtin.file module -- Manage files and file properties — Ansible Core Documentationを参照してください。

5.1 ファイルを作成する方法

ファイルを作成するplaybookファイルを作成します。fileモジュールに対して、以下のパラメータを渡します。
・ファイル名(path: /tmp/test.txt)
・ファイル作成(state: touch)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Create file
      ansible.builtin.file:
        path: /tmp/test.txt
        state: touch

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ***************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************
ok: [node1]

TASK [Create file] *******************************************************************************************************
changed: [node1]

PLAY RECAP ***************************************************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

作成したファイルを確認します。

[root@node1 ~]# ls -l /tmp/test.txt
-rw-r--r--. 1 root root 0  2月 21 19:26 /tmp/test.txt

なお、モジュールに渡すパラメータは、以下のようにansible-docコマンドを使って調べることができます。

[root@control ~]# ansible-doc file
> ANSIBLE.BUILTIN.FILE    (/usr/lib/python3.9/site-packages/ansible/modules/file.py)

        Set attributes of files, directories, or symlinks and their targets.
        Alternatively, remove files, symlinks or directories. Many other modules
        support the same options as the `file' module - including
        [ansible.builtin.copy], [ansible.builtin.template], and
        [ansible.builtin.assemble]. For Windows targets, use the
        [ansible.windows.win_file] module instead.
-snip-

5.2 ファイルを削除する方法

ファイルを削除するplaybookファイルを作成します。fileモジュールに対して、以下のパラメータを渡します。
・ファイル名(path: /tmp/test.txt)
・ファイル削除(state: absent)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Delete file
  hosts: nodes
  tasks:
    - name: Remove file
      ansible.builtin.file:
        path: /tmp/test.txt
        state: absent

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Delete file] *******************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************
ok: [node1]

TASK [Remove file] *******************************************************************************************************
changed: [node1]

PLAY RECAP ***************************************************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ファイルを確認します。ファイルが削除されたことがわかります。

[root@node1 ~]# ls -l /tmp/test.txt
ls: '/tmp/test.txt' にアクセスできません: そのようなファイルやディレクトリはありません

5.3 ディレクトリを作成する方法

ディレクトリを作成するplaybookファイルを作成します。fileモジュールに対して、以下のパラメータを渡します。
・作成するディレクトリのパス名(path: /tmp/test)
ディレクトリ作成(state: directory)
・アクセスモード(mode: '0600')

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Create a directory if it does not exist
      ansible.builtin.file:
        path: /tmp/test
        state: directory
        mode: '0600'

playbook実行前に、ターゲットノードに/tmp/testディレクトリが存在しないことを確認します。

[root@node1 ~]# ls -ld /tmp/test
ls: '/tmp/test' にアクセスできません: そのようなファイルやディレクトリはありません

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ***************************************************************************************************

TASK [Gathering Facts] ***************************************************************************************************
ok: [node1]

TASK [Create a directory if it does not exist] ***************************************************************************
ok: [node1]

PLAY RECAP ***************************************************************************************************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行すると、/tmp/testディレクトリが作成されたことがわかります。

[root@node1 ~]# ls -ld /tmp/test
drw-------. 2 root root 6  2月 18 20:27 /tmp/test

6 statモジュールの使い方

statは、ファイルやディレクトリの状態に関する情報を取得するためのモジュールです。
詳細は、ansible.builtin.stat module -- Retrieve file or file system status — Ansible Core Documentationを参照してください。

(1) 全ての情報を取得する方法
/etc/chrony.confの状態を取得するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Check the status of /etc/chrony.conf
      ansible.builtin.stat:
        path: /etc/chrony.conf
      register: result

    - name: Display file information
      ansible.builtin.debug:
        var: result

playbookを実行します。ターゲットノードの/etc/chrony.confに関する様々な状態が取得できることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [node1]

TASK [Check the status of /etc/chrony.conf] *******************************************************
ok: [node1]

TASK [Display file information] *******************************************************************
ok: [node1] => {
    "result": {
        "changed": false,
        "failed": false,
        "stat": {
            "atime": 1709638065.1771483,
            "attr_flags": "",
            "attributes": [],
            "block_size": 4096,
            "blocks": 8,
            "charset": "us-ascii",
            "checksum": "4acaa3c2b1957b7fee0d60a5c9d9137ed4ef85fc",
            "ctime": 1703065758.2565594,
            "dev": 64771,
            "device_type": 0,
            "executable": false,
            "exists": true,
            "gid": 0,
            "gr_name": "root",
            "inode": 34489641,
            "isblk": false,
            "ischr": false,
            "isdir": false,
            "isfifo": false,
            "isgid": false,
            "islnk": false,
            "isreg": true,
            "issock": false,
            "isuid": false,
            "mimetype": "text/plain",
            "mode": "0644",
            "mtime": 1661778273.0,
            "nlink": 1,
            "path": "/etc/chrony.conf",
            "pw_name": "root",
            "readable": true,
            "rgrp": true,
            "roth": true,
            "rusr": true,
            "size": 1374,
            "uid": 0,
            "version": "1460430642",
            "wgrp": false,
            "woth": false,
            "writeable": true,
            "wusr": true,
            "xgrp": false,
            "xoth": false,
            "xusr": false
        }
    }
}

PLAY RECAP ****************************************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

(2) 特定の情報を取得する方法
次は、/etc/chrony.confの実行権限を取得するplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: nodes
  tasks:
    - name: Check the status of /etc/chrony.conf
      ansible.builtin.stat:
        path: /etc/chrony.conf
      register: result

    - name: Display file information
      ansible.builtin.debug:
        var: result.stat.mode

playbookを実行すると、/etc/chrony.confの実行権限が0644であることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ****************************************************************************

TASK [Gathering Facts] ****************************************************************************
ok: [node1]

TASK [Check the status of /etc/chrony.conf] *******************************************************
ok: [node1]

TASK [Display file information] *******************************************************************
ok: [node1] => {
    "result.stat.mode": "0644"
}

PLAY RECAP ****************************************************************************************
node1                      : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでstatコマンドを実行します。/etc/chrony.confの実行権限が0644であることがわかります。

[root@node1 ~]# stat /etc/chrony.conf
  File: /etc/chrony.conf
  Size: 1374            Blocks: 8          IO Block: 4096   通常ファイル
Device: fd03h/64771d    Inode: 34489641    Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Context: system_u:object_r:etc_t:s0
Access: 2024-02-25 20:44:07.805680323 +0900
Modify: 2022-08-29 22:04:33.000000000 +0900
Change: 2023-12-20 18:49:18.256559259 +0900
 Birth: 2023-12-20 18:49:18.255559249 +0900

7 copyモジュールの使い方

copyは、ローカルマシンからリモートマシンにファイルを転送したり(デフォルトの動作)、リモートマシン内でファイルをコピーするモジュールです。ここでは、remote_srcパラメータの動作確認をしてみます。詳細は、ansible.builtin.copy module -- Copy files to remote locations — Ansible Core Documentationを参照してください。

(1) 事前準備
ローカルマシン(コントロールノード)でテスト用のファイルを作成します。

[root@control ~]# vi /root/test.txt
[root@control ~]# cat /root/test.txt
This is a control node.

リモートマシン(ターゲットノード)でテスト用のファイルを作成します。

[root@node1 ~]# vi /root/test.txt
[root@node1 ~]# cat /root/test.txt
This is a managed node.

(2) 動作確認(ローカルマシンからリモートマシンにファイルの転送を確認する)
ファイルを転送するplaybookファイルを作成します。copyモジュールに対して、以下のパラメータを渡します。remote_srcパラメータはplaybookファイルに記載していないので、デフォルトのfalseとして動作します。わかりやすくするため、明にremote_src: falseと記載してもよいです。
・ファイルのコピー元(src: /root/test.txt)
・ファイルのコピー先(dest: /tmp/)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Copy file with permissions
      ansible.builtin.copy:
        src: /root/test.txt
        dest: /tmp/

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************
ok: [192.168.122.220]

TASK [Copy file with permissions] ******************************************************************************************
ok: [192.168.122.220]

PLAY RECAP *****************************************************************************************************************
192.168.122.220            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

リモートマシン(ターゲットノード)でファイルの内容を確認します。デフォルトは、ローカルマシンからリモートマシンにファイルを転送するので、test.txtファイルの内容が"This is a control node."となっていることがわかります。

[root@node1 ~]# cat /tmp/test.txt
This is a control node.

(3) 動作確認(リモートマシン内でファイルのコピーを確認する)
ファイルを転送するplaybookファイルを作成します。copyモジュールに対して、以下のパラメータを渡します。remote_srcパラメータにtrueと指定したので、ファイルのコピー元は、リモートマシンとなります。
・ファイルのコピー元(src: /root/test.txt)
・ファイルのコピー先(dest: /tmp/)
・ファイルのコピー元の指定(remote_src: true)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Copy file with permissions
      ansible.builtin.copy:
        src: /root/test.txt
        dest: /tmp/
        remote_src: true

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************
ok: [192.168.122.220]

TASK [Copy file with permissions] ******************************************************************************************
changed: [192.168.122.220]

PLAY RECAP *****************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

リモートマシン(ターゲットノード)でファイルの内容を確認します。リモートマシン内でファイルを転送するので、test.txtファイルの内容が"This is a managed node."となっていることがわかります。

[root@node1 ~]# cat /tmp/test.txt
This is a managed node.

8 unarchiveモジュールの使い方

unarchiveは、アーカイブファイルを展開するモジュールです。詳細は、
ansible.builtin.unarchive module -- Unpacks an archive after (optionally) copying it from the local machine — Ansible Core Documentationを参照してください。

アーカイブの展開方法として次の2つの動作確認をします。

方法 内容
その1 ローカルマシン(コントロールノード)にアーカイブファイルをダウンロード。それをリモートマシン(ターゲットノード)に転送して展開
その2 リモートマシン(ターゲットノード)にアーカイブファイルをダウンロードして展開

8.1 その1

事前準備として、ローカルマシン(コントロールノード)にhttpd-2.4.58.tar.gzをダウンロードします。

[root@control ~]# wget -P /root https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz

ダウンロードしたファイルを確認します。

[root@control ~]# ls -l httpd-2.4.58.tar.gz
-rw-r--r--. 1 root root 9825177 10月 19 18:09 httpd-2.4.58.tar.gz

アーカイブファイルを展開するplaybookファイルを作成します。unarchiveモジュールに対して、以下のパラメータを渡します。
アーカイブファイルの格納ディレクトリ(/root/httpd-2.4.58.tar.gz)
アーカイブファイルの展開先ディレクトリ(dest: /tmp)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Unarchive httpd-2.4.58.tar.gz to /tmp
      ansible.builtin.unarchive:
        src: /root/httpd-2.4.58.tar.gz
        dest: /tmp
        remote_src: false

playbookを実行する前に、リモートマシン(ターゲットノード)にhttpdディレクトリが存在しないことを確認します。

[root@node1 ~]# ls -ld /tmp/httpd*
ls: '/tmp/httpd*' にアクセスできません: そのようなファイルやディレクトリはありません

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************
ok: [192.168.122.220]

TASK [Unarchive httpd-2.4.58.tar.gz to /tmp] *******************************************************************************
changed: [192.168.122.220]

PLAY RECAP *****************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行したあと、/tmp配下を確認すると、httpd-2.4.58ディレクトリが作成されたことがわかります。

[root@node1 ~]# ls -ld /tmp/httpd*
drwxr-xr-x. 13 504 games 4096 10月 17 00:06 /tmp/httpd-2.4.58

次の動作検証をするため、作成したディレクトリを削除します。

[root@node1 ~]# rm -fr /tmp/httpd-2.4.58/
[root@node1 ~]# ls -ld /tmp/httpd*
ls: '/tmp/httpd*' にアクセスできません: そのようなファイルやディレクトリはありません

8.2 その2

リモートマシン(ターゲットノード)でアーカイブファイルを展開するplaybookファイルを作成します。unarchiveモジュールに対して、以下のパラメータを渡します。
アーカイブの格納ディレクトリ(src: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz)
アーカイブの展開先ディレクトリ(dest: /tmp)
アーカイブファイルの展開場所(emote_src: true)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Unarchive httpd-2.4.58.tar.gz to /tmp
      ansible.builtin.unarchive:
        src: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz
        dest: /tmp
        remote_src: true

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *****************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************
ok: [192.168.122.220]

TASK [Unarchive httpd-2.4.58.tar.gz to /tmp] *******************************************************************************
changed: [192.168.122.220]

PLAY RECAP *****************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

playbookを実行したあと、リモートマシン(ターゲットノード)で/tmp配下を確認すると、httpd-2.4.58ディレクトリが作成されたことがわかります。

[root@node1 ~]# ls -ld /tmp/httpd*
drwxr-xr-x. 13 504 games 4096 10月 17 00:06 /tmp/httpd-2.4.58

9 userモジュールの使い方

userは、ユーザの追加・削除等を行うモジュールです。詳細は、ansible.builtin.user module -- Manage user accounts — Ansible Core Documentation

9.1 ユーザを追加する方法

UID=2000のユーザ(user1)を追加するplaybookファイルを作成します。
userモジュールに対して、以下のパラメータを渡します。
・ユーザ名(name: user1)
・passwdファイルに記載するコメント(comment: test)
・ユーザID(uid: 2000)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Add the user 'user1' with a specific uid.
      ansible.builtin.user:
        name: user1
        comment: test comment
        uid: 2000

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
ok: [192.168.122.220]

TASK [Add the user 'user1' with a specific uid.] *****************************************************************
ok: [192.168.122.220]

PLAY RECAP *******************************************************************************************************
192.168.122.220            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでユーザを確認します。UID=2000のuser1が追加されたことがわかります。

[root@node1 ~]# grep user1 /etc/passwd
user1:x:2000:2000:test comment:/home/user1:/bin/bash

9.2 ユーザを削除する方法

ユーザ(user1)を削除するplaybookファイルを作成します。
userモジュールに対して、以下のパラメータを渡します。
・ユーザ名(name: user1)
・ユーザの削除(state: absent)

[root@control ~]# vi test.yml
[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Remove the user 'user1'.
      ansible.builtin.user:
        name: user1
        state: absent

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
ok: [192.168.122.220]

TASK [Remove the user 'user1'.] **********************************************************************************
ok: [192.168.122.220]

PLAY RECAP *******************************************************************************************************
192.168.122.220            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでユーザを確認します。user1が削除されたことがわかります。

[root@node1 ~]# grep user1 /etc/passwd
[root@node1 ~]#

10 dnfモジュールの使い方

dnfは、パッケージのインストールやアンインストールをするモジュールです。詳細は、ansible.builtin.dnf module -- Manages packages with the dnf package manager — Ansible Core Documentationを参照してください。

10.1 パッケージをインストールする方法

最新のhttpdパッケージをインストールするplaybookファイルを作成します。
dnfモジュールに対して、以下のパラメータを渡します。
・パッケージ名(name: httpd)
・最新パッケージのインストール(state: latest)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Install the latest version of Apache
      ansible.builtin.dnf:
        name: httpd
        state: latest

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
Enter passphrase for key '/root/.ssh/id_ed25519':
ok: [192.168.122.220]

TASK [Install the latest version of Apache] *****************************************************************************
changed: [192.168.122.220]

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでパッケージを確認します。httpdパッケージがインストールされたことが確認できます。

[root@node1 ~]# rpm -qa|grep httpd-2
httpd-2.4.57-5.el9.x86_64

10.2 パッケージをアンインストールする方法

httpdパッケージをアンインストールするplaybookファイルを作成します。
dnfモジュールに対して、以下のパラメータを渡します。
・パッケージ名(name: httpd)
・パッケージの削除(state: absent)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Remove the Apache package
      ansible.builtin.dnf:
        name: httpd
        state: absent

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [192.168.122.220]

TASK [Remove the Apache package] ****************************************************************************************
changed: [192.168.122.220]

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでパッケージを確認します。httpdパッケージが削除されたことがわかります。

[root@node1 ~]# rpm -qa|grep httpd-2
[root@node1 ~]#

11 serviceモジュールの使い方

serviceは、サービスの起動・停止等をおこなうモジュールです。詳細は、ansible.builtin.service module -- Manage services — Ansible Core Documentationを参照してください。

11.1 サービスを起動・停止する方法

httpdパッケージをインストールしたあと、httpdサービスを起動するplaybookファイルを作成します。
serviceモジュールに対して、以下のパラメータを渡します。
・パッケージ名(name: httpd)
・サービスの状態(state: started)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Install httpd package
      ansible.builtin.dnf:
        name: httpd
        state: latest

    - name: Start service httpd, if not started
      ansible.builtin.service:
        name: httpd
        state: started

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
ok: [192.168.122.220]

TASK [Install httpd package] *************************************************************************************
ok: [192.168.122.220]

TASK [Start service httpd, if not started] ***********************************************************************
ok: [192.168.122.220]

PLAY RECAP *******************************************************************************************************
192.168.122.220            : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

ターゲットノードでhttpdサービスの状態を確認すると、active になっていることがわかります。

[root@node1 ~]# systemctl status httpd
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: active (running) since Tue 2024-02-20 20:32:26 JST; 12min ago
       Docs: man:httpd.service(8)
   Main PID: 3874 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 213 (limit: 23186)
     Memory: 29.2M
        CPU: 1.489s
     CGroup: /system.slice/httpd.service
             tq3874 /usr/sbin/httpd -DFOREGROUND
             tq3875 /usr/sbin/httpd -DFOREGROUND
             tq3876 /usr/sbin/httpd -DFOREGROUND
             tq3877 /usr/sbin/httpd -DFOREGROUND
             mq3878 /usr/sbin/httpd -DFOREGROUND

 2月 20 20:32:26 node1 systemd[1]: Starting The Apache HTTP Server...
 2月 20 20:32:26 node1 httpd[3874]: AH00558: httpd: Could not reliably determine the server's fully qualif>
 2月 20 20:32:26 node1 httpd[3874]: Server configured, listening on: port 80
 2月 20 20:32:26 node1 systemd[1]: Started The Apache HTTP Server.

11.2 サービスの自動起動を有効/無効にする方法

ターゲットノードでhttpdサービスの自動起動の状態を確認します。自動起動は無効になっていることがわかります。

[root@node1 ~]# systemctl is-enabled httpd
disabled

httpdサービスの自動起動を有効にするplaybookファイルを作成します。
serviceモジュールに対して、以下のパラメータを渡します。
・パッケージ名(name: httpd)
自動起動の指定(enabled: true)

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Enable auto-start
      ansible.builtin.service:
        name: httpd
        enabled: true

playbookを実行します。

[root@control ~]# vi test.yml
[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] *******************************************************************************************

TASK [Gathering Facts] *******************************************************************************************
ok: [192.168.122.220]

TASK [Enable auto-start] *****************************************************************************************
changed: [192.168.122.220]

PLAY RECAP *******************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでhttpdサービスの自動起動が有効になったことがわかります。

[root@node1 ~]# systemctl is-enabled httpd
enabled

12 get_urlモジュールの使い方

get_urlは、リモートサーバから、HTTP, HTTPS, FTPを使ってファイルをダウンロードするモジュールです。詳細は、ansible.builtin.get_url module -- Downloads files from HTTP, HTTPS, or FTP to node — Ansible Core Documentationを参照してください。

12.1 ファイルをダウンロードする方法

Apache のWebサーバからhttpd-2.4.58.tar.gzをダウンロードするplaybookファイルを作成します。
https://httpd.apache.org/download.cgi#apache24

get_urlモジュールに対して、以下のパラメータを渡します。
・URL(url: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz)
・ダウンロードするファイルを格納するパス(dest: /tmp/)
・ダウンロードしたファイルに付与する実行権(mode: '0444')

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Download httpd-2.4.58.tar.gz from Apache web server.
      ansible.builtin.get_url:
        url: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz
        dest: /tmp/
        mode: '0444'

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [192.168.122.220]

TASK [Download httpd-2.4.58.tar.gz from Apache web server.] *************************************************************
changed: [192.168.122.220]

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

ターゲットノードでダウンロードしたファイル名、実行権を確認します。

[root@node1 ~]# ls -l /tmp/httpd-2.4.58.tar.gz
-r--r--r--. 1 root root 9825177  2月 19 21:28 /tmp/httpd-2.4.58.tar.gz

12.2 ダウンロードするファイルのチェックサムを実行する方法

12.2.1 正しいチェックサムファイルを指定した場合
[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Download file with checksum url (sha256)
      ansible.builtin.get_url:
        url: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz
        dest: /tmp/
        checksum: sha256:https://downloads.apache.org/httpd/httpd-2.4.58.tar.gz.sha256

playbookを実行します。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [192.168.122.220]

TASK [Download file with checksum url (sha256)] *************************************************************************
ok: [192.168.122.220]

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#
12.2.2 誤ったチェックサムファイルを指定した場合

checksumパラメータに対して、誤ったチェックサムを指定したplaybookを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Download file with checksum url (sha256)
      ansible.builtin.get_url:
        url: https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz
        dest: /tmp/
        checksum: sha256:https://downloads.apache.org/httpd/httpd-2.4.58.tar.gz.sha512

誤ったチェックサムファイルを指定すると、以下のようにエラー(failed=1)となります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] **************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************
ok: [192.168.122.220]

TASK [Download file with checksum url (sha256)] *************************************************************************
fatal: [192.168.122.220]: FAILED! => {"changed": true, "checksum_dest": null, "checksum_src": "cd04721a2d9abfc634c895853cd555ac659b81e8", "dest": "/tmp/httpd-2.4.58.tar.gz", "elapsed": 1, "msg": "The checksum for /tmp/httpd-2.4.58.tar.gz did not match 5c11faf0572035ef67b27775d975999411c689cb774553175299a9e99b63d3d7138b0c7f15048ec28038494d8513689f916202c2289d557947d8b190d46ca9f3; it was 503a7da4a4a27fd496037998b17078dc9fe004db32c657c96cce8356b8aa2eb6.", "src": "/root/.ansible/tmp/ansible-tmp-1708347415.2509441-1783-155710284696734/tmpvfp67ljr", "url": "https://dlcdn.apache.org/httpd/httpd-2.4.58.tar.gz"}

PLAY RECAP **************************************************************************************************************
192.168.122.220            : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

[root@control ~]#

13 commandモジュールの使い方

commandは、対象ホストでコマンドを実行するモジュールです。詳細は、ansible.builtin.command module -- Execute commands on targets — Ansible Core Documentationを参照してください。

次の内容のplaybookファイルを作成します。commandモジュールに対してhostnameコマンドをパラメータとして渡し、registerディレクティブを使ってhostnameコマンドの実行結果をvar_hostname変数に格納します。そして、debugモジュールを使って、var_hostname変数に保存されている内容を標準出力に出力します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Execute the hostname command
      ansible.builtin.command:
        cmd: hostname
      register: var_hostname
    - name: Display hostname command result
      ansible.builtin.debug:
        var: var_hostname.stdout

playbookを実行すると、ターゲットノードのホスト名(node1)が表示されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ********************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [192.168.122.220]

TASK [Execute the hostname command] *******************************************************
changed: [192.168.122.220]

TASK [Display hostname command result] ****************************************************
ok: [192.168.122.220] => {
    "var_hostname.stdout": "node1"
}

PLAY RECAP ********************************************************************************
192.168.122.220            : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

14 scriptモジュールの使い方

scriptは、ローカルマシン(コントロールノード)のスクリプトをリモートマシン(ターゲットノード)に転送して実行するモジュールです。詳細は、ansible.builtin.script module -- Runs a local script on a remote node after transferring it — Ansible Core Documentationを参照してください。

(1) 事前準備
ホスト名を表示するスクリプトをローカルマシン(コントロールノード)で作成します。

[root@control ~]# vi /tmp/test.sh
[root@control ~]# cat /tmp/test.sh
#!/usr/bin/bash
hostname

(2) 動作確認
playbookファイルを作成します。

[root@control ~]# vi test.yml
[root@control ~]# cat test.yml
- name: Sample Playbook
  hosts: node1
  tasks:
    - name: Run a script
      ansible.builtin.script:
        cmd: /tmp/test.sh
      register: var_hostname
    - name: Display hostname command result
      ansible.builtin.debug:
        var: var_hostname.stdout

playbookを実行すると、ターゲットノードのホスト名(node1)が表示されていることがわかります。

[root@control ~]# ansible-playbook -i hosts.ini test.yml

PLAY [Sample Playbook] ********************************************************************

TASK [Gathering Facts] ********************************************************************
ok: [192.168.122.220]

TASK [Run a script] ***********************************************************************
changed: [192.168.122.220]

TASK [Display hostname command result] ****************************************************
ok: [192.168.122.220] => {
    "var_hostname.stdout": "node1\r\n"
}

PLAY RECAP ********************************************************************************
192.168.122.220            : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@control ~]#

CPU使用率、メモリ使用量、ディスクアクセスの高いプロセスの調べ方

1 はじめに

システムを運用している中で、コマンドの応答が遅くなるなどの問題が発生する場合があります。このような状況では、CPUやメモリなどのコンピュータリソースを多く使用しているプロセスを特定し、問題の原因を特定する必要があります。リソースを多く消費しているプロセスを見つける方法はいくつかありますが、ここではpsコマンドを使用してCPU使用率、メモリ使用率、およびディスクアクセスが高いプロセスを調べる方法について説明します。なお、psコマンドの詳細な使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 8.6 (Sky Tiger)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
4.18.0-372.9.1.el8.x86_64

3 CPU使用率の高いプロセスを調べる方法(--sort=-%cpu)

3.1 事前準備

私の環境は、CPU使用率の高いプロセスが少なく実行結果を確認しずらいので、意図的にCPU使用率の高いプロセスを起動しました。stress-ngコマンドを実行して、CPU使用率が10%,20%のプロセスをそれぞれ1つ起動します。なお、stress-ngコマンドのインストール方法、使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# stress-ng -k -c 1 -l 10 -q &
[1] 1515
[root@server ~]# stress-ng -k -c 1 -l 20 -q &
[2] 1517

3.2 動作確認

プロセスのCPU使用率を降順に表示するには、psコマンドのオプションに--sort=-%cpuと指定します。以下の実行例を確認すると、プロセスのCPU使用率が降順に表示されていることがわかります。CPU使用率を昇順に表示するには、--sortオプションに+%cpuを指定します。

[root@server ~]# ps ax --sort=-%cpu -o command,pid,%cpu|head -n 5
COMMAND                       PID %CPU
stress-ng -k -c 1 -l 20 -q   1518 21.2
stress-ng -k -c 1 -l 10 -q   1516 10.5
/usr/lib/systemd/systemd --     1  1.5
/usr/bin/python2 -Es /usr/s   616  1.3

pkillコマンドを実行して、stressプロセスを終了します。

[root@server ~]# pkill stress-ng

4 メモリ使用量の高いプロセスを調べる方法(--sort=-rss)

4.1 事前準備

stressコマンドを実行して、512M,256Mのメモリを獲得するプロセスをそれぞれ1つ起動します。なお、stressコマンドの詳細な使い方は、stressコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# stress -m 1 --vm-bytes 256M --vm-hang 0 -q &
[1] 1543
[root@server ~]# stress -m 1 --vm-bytes 512M --vm-hang 0 -q &
[2] 1545

4.2 動作確認

プロセスが使用しているメモリ使用量(RSS)を降順に表示します。以下の実行例を確認すると、プロセスのメモリ使用量が降順に表示されていることがわかります。

[root@server ~]# ps ax --sort=-rss -o command,pid,ppid,vsz,rss|head -n 5
COMMAND                       PID  PPID    VSZ   RSS
stress -m 1 --vm-bytes 512M  1546  1545 531600 524456
stress -m 1 --vm-bytes 256M  1544  1543 269456 262288
/usr/bin/python2 -Es /usr/s   616     1 359088 29808
/usr/bin/python2 -Es /usr/s   937     1 574280 19484

pkillコマンドを実行して、stressプロセスを終了します。

[root@server ~]# pkill stress
[1]-  Terminated              stress -m 1 --vm-bytes 256M --vm-hang 0 -q
[2]+  Terminated              stress -m 1 --vm-bytes 512M --vm-hang 0 -q

5 ディスクアクセスの割合が高いプロセスを調べる方法

iotopコマンドを使って、ディスクアクセスが多いプロセスを調べてみます。なお、iotopコマンドの詳細な使い方は、iotopコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# iotop

stressコマンドを実行します。

[root@server ~]# stress -d 1 -q &
[1] 4682

以下の実行例を確認すると、stressコマンドで起動したプロセスがiotopコマンド実行結果の最上位に表示されていることがわかります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

stress-ngコマンドの使い方(UDP編)

1 はじめに

stress-ngコマンドは、CPU、メモリ、ディスク、ネットワークなど、さまざまなリソースに対して負荷テストを行うためのツールです。stressコマンドと比較して、より多くの種類のリソースに対して負荷をかけることができます。本記事ではUDPに関する使い方を説明します。なお、stressコマンド、およびstress-ngコマンドの基本的な使い方は、以下の記事を参照してください。

2 検証環境

AlmaLinuxの版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

3 パッケージのインストール方法

stress-ngパッケージをインストールします。なお、dnfコマンドの使い方は、dnfコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# dnf -y install stress-ng

stress-ngコマンドの版数を確認します。

[root@server ~]# stress-ng -V
stress-ng, version 0.15.00 (gcc 11.3, x86_64 Linux 5.14.0-284.11.1.el9_2.x86_64) ?諮

なお、AlmaLinux 9.1以前は、stress-ngパッケージはepelリポジトリにあるので、まずepel-releaseパッケージをインストールします。そのあと、stress-ngパッケージをインストールします。

[root@server ~]# dnf -y install epel-release
[root@server ~]# dnf -y install stress-ng

4 オプション一覧

stress-ngコマンドのUDPオプションには以下のものがあります。

[root@server ~]# man stress-ng
-snip-
       --udp N
              start  N workers that transmit data using UDP. This involves a pair of client/server processes perform‐
              ing rapid connect, send and receives and disconnects on the local host.

       --udp-domain D
              specify the domain to use, the default is ipv4. Currently ipv4, ipv6 and unix are supported.

       --udp-if NAME
              use network interface NAME. If the interface NAME does not exist, is not up or does not support the do‐
              main then the loopback (lo) interface is used as the default.

       --udp-gro
              enable UDP-GRO (Generic Receive Offload) if supported.

       --udp-lite
              use the UDP-Lite (RFC 3828) protocol (only for ipv4 and ipv6 domains).

       --udp-ops N
              stop udp stress workers after N bogo operations.

       --udp-port P
              start  at port P. For N udp worker processes, ports P to P - 1 are used. By default, ports 7000 upwards
              are used.

       --udp-flood N
              start N workers that attempt to flood the host with UDP packets to random ports. The IP address of  the
              packets are currently not spoofed. This is only available on systems that support AF_PACKET.

       --udp-flood-domain D
              specify the domain to use, the default is ipv4. Currently ipv4 and ipv6 are supported.

       --udp-flood-if NAME
              use network interface NAME. If the interface NAME does not exist, is not up or does not support the do‐
              main then the loopback (lo) interface is used as the default.

       --udp-flood-ops N
              stop udp-flood stress workers after N bogo operations.

5 送受信のプロセス数を指定する方法(--udp)

--udpオプションは、UDPパケットの送受信プロセス数を指定するためのオプションです。1を指定すると、送受信プロセスがそれぞれ1つずつ生成されます。2を指定すると、送受信プロセスがそれぞれ2つずつ生成されます。あとは同様です。また、-kオプションは、親プロセスと子プロセスを同じ名前にするためのオプションです。psコマンドの実行結果を見ると、全てのプロセスの名前がstress-ngであることがわかります。

[root@server ~]# stress-ng -k --udp 1
stress-ng: info:  [1541] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1541] dispatching hogs: 1 udp

psコマンドを使用してプロセスの状態を確認します。また、後述するstraceコマンドの実行結果から分かるように、PID=1542はUDPパケットの受信プロセス、PID=1543はUDPパケットの送信プロセスです。また、PID=1541は送受信プロセスの終了をwaitシステムコールで待機しています。do_waitはwaitシステムコールの延長として呼び出されるカーネル関数です。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,wchan
COMMAND             PID    PPID PSR %CPU WCHAN
stress-ng          1541    1515   3  0.0 do_wait
stress-ng          1542    1541   1 43.5 -
stress-ng          1543    1542   3 49.7 -

次に、lsofコマンドを実行します。PID=1542はUDPパケットの受信プロセスです。UDPの7000番ポートで受信待ちしていることがわかります。そして、PID=1543はUDPパケットの送信プロセスです。UDPパケットをポート番号60301から送信します。また、それぞれのプロセスは、任意のIPアドレス(ポート番号の前のIPアドレスワイルドカード(*)なので)でUDPパケットの送受信を実行することがわかります。なお、lsofコマンドの使い方は、lsofコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lsof -c stress-ng -a -i -a -nP
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
stress-ng 1542 root    4u  IPv4  21463      0t0  UDP *:7000
stress-ng 1543 root    4u  IPv4  23423      0t0  UDP *:60301

PID=1543のプロセスに対して、straceコマンドを実行します。 PID=1543のプロセスはsendtoシステムコールを繰り返し実行しています。宛先UDPポート番号は7000、送信元IPアドレスは任意のIPアドレス(0.0.0.0)であることがわかります。なお、straceコマンドの使い方は、straceコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]#  strace -e trace=network -p 1543
strace: Process 1543 attached
sendto(4, "QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ"..., 304, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 304
sendto(4, "RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR"..., 320, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 320
sendto(4, "SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS"..., 336, 0, {sa_family=AF_INET, sin_port=htons(7000), sin_addr=inet_addr("0.0.0.0")}, 16) = 336
-snip-

PID=1542のプロセスに対して、straceコマンドを実行します。PID=1542のプロセスはrecvfromシステムコールを繰り返し実行しています。送信元UDPポート番号は60301、送信元IPアドレス127.0.0.1であることがわかります。

[root@server ~]#  strace -e trace=network -p 1542
strace: Process 1542 attached
recvfrom(4, "MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 960
recvfrom(4, "NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 976
recvfrom(4, "OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 992
recvfrom(4, "PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP"..., 1024, 0, {sa_family=AF_INET, sin_port=htons(60301), sin_addr=inet_addr("127.0.0.1")}, [16]) = 1008
-snip-

tcpdumpコマンドを実行すると、loデバイスを介してUDPパケットを送受信していることがわかります。なお、tcpdumpコマンドの使い方は、tcpdumpの使い方(基本編) - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:01:24.361797 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 86 (480)
20:01:24.361805 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 87 (496)
20:01:24.361813 IP 127.0.0.1.60301 > 127.0.0.1.7000:  rx type 88 (512)
-snip-

Ctrl+cを押下してstress-ngコマンドを終了します。

[root@server ~]# stress-ng -k --udp 1
stress-ng: info:  [1541] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1541] dispatching hogs: 1 udp
^Cstress-ng: info:  [1541] successful run completed in 653.52s (10 mins, 53.52 secs)

6 受信待ちのポート番号を指定する方法(--udp-port)

--udp-port オプションは、UDPパケットの到着を待ち受けるポート番号を指定するためのオプションです。デフォルトではポート番号7000が使用されますが、もし他のプロセスが同じポート番号(7000)をすでに使用している場合、このオプションを使用して、stress-ngプロセスの受信ポート番号を変更することができます。

[root@server ~]# stress-ng -k --udp 1 --udp-port 8000
stress-ng: info:  [1790] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1790] dispatching hogs: 1 udp

lsofコマンドを実行すると、stress-ngプロセスが8000番ポートでUDPパケットの受信待ちをしていることが確認できます。

[root@server ~]# lsof -c stress-ng -a -i -a -nP
COMMAND    PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
stress-ng 1791 root    4u  IPv4  25883      0t0  UDP *:8000
stress-ng 1792 root    4u  IPv4  23820      0t0  UDP *:48625

7 IPv4またはIPv6を指定する方法(--udp-domain)

--udp-domainオプションは、UDPパケットの送受信において、IPv4またはIPv6を使用するかを指定するオプションです。デフォルトでは、UDPパケットの送受信にはIPv4が利用されますが、このオプションを使用することでIPv6を選択することが可能です

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行します。

[root@server ~]# tcpdump -i lo -nn

--udp-domainオプションにipv6を指定して、stress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp 1 --udp-domain ipv6
stress-ng: info:  [1314] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1314] dispatching hogs: 1 udp
stress-ng: info:  [1314] successful run completed in 0.01s

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行すると、ループバックアドレスIPv6UDPパケットが送受信されていることが確認できます。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:16:11.211740 IP6 ::1.60769 > ::1.7000:  [|rx] (16)
20:16:11.211915 IP6 ::1.60769 > ::1.7000:  rx type 66 (32)
20:16:11.211938 IP6 ::1.60769 > ::1.7000:  rx type 67 (48)
20:16:11.211958 IP6 ::1.60769 > ::1.7000:  rx type 68 (64)
-snip-

8 実行回数を指定する方法(--udp-ops)

--udp-opsUDPパケットの送信回数を指定するオプションです。

UDPパケットを5回送信してみます。

[root@server ~]# stress-ng -k --udp 1 --udp-ops 5
stress-ng: info:  [1636] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1636] dispatching hogs: 1 udp
stress-ng: info:  [1636] successful run completed in 0.00s

tcpdumpコマンドの実行結果を確認すると、UDPパケットが5回送信されているのがわかります。

[root@server ~]# tcpdump -i lo -nn
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes
20:24:55.105270 IP 127.0.0.1.47176 > 127.0.0.1.7000:  [|rx] (16)
20:24:55.105338 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 66 (32)
20:24:55.105361 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 67 (48)
20:24:55.105380 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 68 (64)
20:24:55.105398 IP 127.0.0.1.47176 > 127.0.0.1.7000:  rx type 69 (80)

9 UDPパケット送受信するインタフェースを指定する方法(--udp-if)

--udp-ifオプションは、UDPパケットの送受信に使用するインタフェースを指定するオプションですが、実験をしてみると正常に機能していないようにみえます。

--udp-ifオプションにeth0を指定してstress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp 1 --udp-if eth0 --udp-ops 3
stress-ng: info:  [1235] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1235] dispatching hogs: 1 udp
stress-ng: info:  [1235] successful run completed in 0.01s

tcpdumpコマンドの実行結果を確認すると、インタフェースにeth0を指定したにもかかわらず、lo(127.0.0.1)を使用しています。

[root@server ~]# tcpdump -i any udp port 7000 -nn
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
20:03:14.857652 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  [|rx] (16)
20:03:14.857752 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 66 (32)
20:03:14.857768 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 67 (48)
20:03:14.857779 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 68 (64)
20:03:14.857789 lo    In  IP 127.0.0.1.51858 > 127.0.0.1.7000:  rx type 69 (80)
-snip-

10 受信待ちのポート番号をランダムに指定する方法(--udp-flood)

--udp-floodオプションは、UDPパケットの受信ポート番号をランダムに指定するオプションです。

送受信するUDPパケットを確認するため、tcpdumpコマンドを実行します。

[root@server ~]# tcpdump -i any udp -nn

stress-ngコマンドを実行します。

[root@server ~]# stress-ng -k --udp-flood 1 --udp-flood-ops 3
stress-ng: info:  [1273] defaulting to a 86400 second (1 day, 0.00 secs) run per stressor
stress-ng: info:  [1273] dispatching hogs: 1 udp-flood
stress-ng: info:  [1273] successful run completed in 0.00s

tcpdumpコマンドの実行結果を確認すると、UDPパケットの受信ポート番号が1025,31282,1026と変化していることがわかります。

[root@server ~]# tcpdump -i any udp -nn
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
20:14:23.005442 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.1025: UDP, length 1
20:14:23.005498 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.31282: UDP, length 1
20:14:23.005543 lo    In  IP 127.0.0.1.36690 > 127.0.0.1.1026: UDP, length 2

11 UDP-Liteのパケットを送受信する方法(--udp-lite)

私の環境では、CONFIG_UDPLITEが定義されていないので、実験することができませんでした。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

perf topコマンドの使い方

1 perf topコマンドとは?

perfコマンドはLinuxのパフォーマンス分析に使用します。perfコマンドには多くのサブコマンドが含まれており、その中でtopサブコマンドは主にパフォーマンス解析やトラブルシューティングのために利用します。なお、manページではこれらのサブコマンドを「コマンド」と表現していますが、混同を避けるため、この記事では「サブコマンド」と呼ぶことにします。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

3 インストール方法

perfパッケージをインストールします。

[root@server ~]# dnf -y install perf

perfコマンドの版数を確認します。

[root@server ~]# perf -v
perf version 5.14.0-284.30.1.el9_2.x86_64

4 コマンド一覧

perfコマンドには、以下のサブコマンドがあります。

[root@server ~]# perf -h

 usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]

 The most commonly used perf commands are:
   annotate        Read perf.data (created by perf record) and display annotated code
   archive         Create archive with object files with build-ids found in perf.data file
   bench           General framework for benchmark suites
   buildid-cache   Manage build-id cache.
   buildid-list    List the buildids in a perf.data file
   c2c             Shared Data C2C/HITM Analyzer.
   config          Get and set variables in a configuration file.
   daemon          Run record sessions on background
   data            Data file related processing
   diff            Read perf.data files and display the differential profile
   evlist          List the event names in a perf.data file
   ftrace          simple wrapper for kernel's ftrace functionality
   inject          Filter to augment the events stream with additional information
   iostat          Show I/O performance metrics
   kallsyms        Searches running kernel for symbols
   kmem            Tool to trace/measure kernel memory properties
   kvm             Tool to trace/measure kvm guest os
   kwork           Tool to trace/measure kernel work properties (latencies)
   list            List all symbolic event types
   lock            Analyze lock events
   mem             Profile memory accesses
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile
   sched           Tool to trace/measure scheduler properties (latencies)
   script          Read perf.data (created by perf record) and display trace output
   stat            Run a command and gather performance counter statistics
   test            Runs sanity tests.
   timechart       Tool to visualize total system behavior during a workload
   top             System profiling tool.
   version         display the version of perf binary
   probe           Define new dynamic tracepoints
   trace           strace inspired tool

 See 'perf help COMMAND' for more information on a specific command.

各サブコマンド(COMMAND)のヘルプは、以下の書式にしたがって表示します。

perf help COMMAND

topサブコマンドのヘルプを表示してみます。

[root@server ~]# perf help top
PERF-TOP(1)                                          perf Manual                                          PERF-TOP(1)

NAME
       perf-top - System profiling tool.

SYNOPSIS
       perf top [-e <EVENT> | --event=EVENT] [<options>]

DESCRIPTION
       This command generates and displays a performance counter profile in real time.

OPTIONS
       -a, --all-cpus
           System-wide collection. (default)
-snip-

5 テストプログラムの作成

perf topコマンドでCPU使用率等を確認するため、テストプログラムを作成します。

[root@server ~]# vi test.c
[root@server ~]# cat test.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define COUNT 1000000

void func11() {}
void func22() {}

void func1()
{
    for (int i = 0; i < COUNT; i++) {
            func11();
    }
    usleep(1000);  // 1ミリ秒のスリープ
}

void func2()
{
    for (int i = 0; i < COUNT; i++) {
            func22();
    }
    usleep(1000);  // 1ミリ秒のスリープ
}

int main()
{
    srand(time(NULL));
    while (1) {
        int random = rand() % 5; // 0から4の乱数を生成

        if (random == 0) {
            func2(); // 20%の確率でfunc2を呼び出す
        } else {
            func1(); // 80%の確率でfunc1を呼び出す
        }
    }
    return 0; // この部分には到達しない
}

テストプログラムをコンパイルします。

[root@server ~]# gcc -g -Wall -o test test.c

6 基本的な使い方

perfコマンドに続けて、topサブコマンドを指定します。終了するには、Ctrl+cを押下します。

[root@server ~]# perf top

実行結果は以下のようになります。各列の意味は以下のとおりです。

列の名前 意味
Overhead 関数またはプロセスが使用している CPU の割合を表しています
Shared Object プログラムまたはライブラリー名を表しています
Symbol [k]はカーネル空間で実行する関数、[.]はユーザ空間で実行する関数を表しています

tasksetコマンドを使って、テストプログムをCPU0で実行します。なお、tasksetコマンドの使い方は、tasksetコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# taskset -c 0 ./test &
[1] 2713

psコマンドを使用して、プロセスの状態を確認します。testプロセスはCPU0(CPU使用率73%程度)で動作していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C test -o comm,pid,ppid,psr,%cpu
COMMAND             PID    PPID PSR %CPU
test               2713    2587   0 72.7

perf topの実行結果を確認すると、testプログラムの関数が表示されていることがわかります。

7 モニタするCPUを指定する方法(-C)

lscpuコマンドを実行して、マシンに搭載されているCPU数を確認すると、4つあることがわかります。なお、lscpuコマンドの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -ae
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 2:2:2:2          yes
  2    0      2    2 4:4:4:4          yes
  3    0      3    3 6:6:6:6          yes

perf topコマンドを実行します。このとき、CPU0で実行している関数名だけを表示するようにします。

[root@server ~]# perf top -C 0

実行結果を確認すると、CPU0で動作しているtestプロセスが表示されていることがわかります。

次に、CPU1,2,3で実行しているプロセスだけを表示するようにします。

[root@server ~]# perf top -C 1,3

実行結果を確認すると、testプロセスが表示されていないことがわかります。

8 カーネルモードで実行している関数を表示する方法(-U)

-Uオプションは、カーネルモードで実行している関数だけを表示するオプションです。

[root@server ~]# perf top -U

実行結果を確認すると、シンボル列が[K]となっているので、カーネルモードで実行している関数だけが表示されていることがわかります。

9 ユーザモードで実行している関数を方法(-K)

-Kオプションは、ユーザモードで実行している関数だけを表示するオプションです。

[root@server ~]# perf top -K

実行結果を確認すると、シンボル列が[.]となっているので、ユーザモードで実行している関数だけが表示されていることがわかります。

10 表示間隔を変更する方法(-d)

-dオプションは、表示間隔を指定するオプションです。以下のように実行すると、情報が10秒ごとに更新されます。

[root@server ~]# perf top -d 10

11 特定の情報を表示する方法(--sort)

commオプションは、コマンド列を表示するオプションです。

[root@server ~]# perf top --sort comm

実行結果を確認すると、コマンド列が表示されていることがわかります。コマンド列と言っていますが、表示しているのはプロセス名です。

pidオプションは、プロセスID(PID)列を表示するオプションです。commオプションとあわせて指定してみます。

[root@server ~]# perf top --sort comm,pid

実行結果を確認すると、コマンド列とPID列が表示されていることがわかります。testプロセスのPIDが2713であることがわかります。

cpuオプションは、プロセスを実行しているCPU列を表示するオプションです。comm,pidオプションとあわせて指定してみます。

[root@server ~]# perf top --sort comm,pid,cpu

実行結果を確認すると、コマンド列,PID列,CPU列が表示されていることがわかります。testプロセスはCPU0で動作していることがわかります。

12 関数の呼び出し関係を表示する方法(-g)

-gオプションは、関数の呼び出し関係を表示するオプションです。

[root@server ~]# perf top -g

↑↓キーを使って、詳細表示したい関数にカーソルをあわせて、Enterキーを押下します。

次に「Expand」の行にカーソルをあわせて、Enterキーを押下します。

実行結果を確認すると、次のような関数呼び出しになっていることがわかります。また、それぞれの関数のCPU使用率が表示されていることもわかります。
・__libc_start_call_main -> main -> func1
・func1 -> func11

13 perfコマンドをリアルタイム優先度で実行する方法(-r)

-rオプションは、 perfコマンドをリアルタイムプロセスで実行するオプションです。指定しないとタイムシェアリングプロセスとして実行します。リアルタイムプロセスで実行することで、確実にパフォーマンスデータが取得できる一方で、他のプロセスに与える影響が大きくなります。

オプションを指定しないでperf topを実行します。

[root@server ~]# perf top

psコマンドを使用すると、perfプロセスはタイムシェアリングプロセスとして実行されていることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             TS      -

perfコマンド をリアルタイムプロセスの最高優先度(1)で実行します。

[root@server ~]# perf top -r 1

psコマンドを使用すると、perfプロセスはリアルタイムプロセス(優先度1)として実行されていることがわかります。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             FF      1

perfコマンド をリアルタイムプロセスの最低優先度(99)で実行します。

[root@server ~]# perf top -r 99

psコマンドを使用すると、perfプロセスはリアルタイムプロセス(優先度99)として実行されていることがわかります。

[root@server ~]# ps -C perf -o comm,cls,rtprio
COMMAND         CLS RTPRIO
perf             FF     99

sarコマンドの%stealの意味について

1 はじめに

1.1 概要

sarコマンドやvmstatコマンドを使用すると、「%steal」という項目が表示されます。この項目は、仮想マシンのvCPUがホストの物理CPUに割り当てられない割合を表しています。この記事では、意図的にvCPUが物理CPUに割り当てられない状況を作り出し、「%steal」がどのように変化するかを検証してみます。

1.2 出力例

sarコマンドを実行すると、右から2列目に「%steal」が表示されていることがわかります。なお、sarコマンドの使い方は、sarコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# sar 1 -P ALL
19時22分50秒	CPU	%user	%nice	%system	%iowait	%steal	%idle
19時22分51秒	all	0.00	0.00	0.00	0.00	0.00	100.00
19時22分51秒	0	0.00	0.00	0.00	0.00	0.00	100.00
-snip-

vmstatコマンドを実行すると、右端に「st」(%steal)が表示されていることがわかります。なお、vmstatコマンドの使い方は、vmstatコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# vmstat 1 -w
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   0    0            0      3368808         1636       185800    0    0    47     2   44   32   0   1  97   2   0
-snip-

1.3 検証方針

「%steal」の変化を確認するため、以下の手順を実行します。
1. 仮想マシンのvCPU(X)を物理CPU(Y)に固定的に割り当てる。
2. vCPU(X)で実行するプロセスより優先度の高いプロセスを物理CPU(Y)で実行して、vCPU(X)で実行するプロセスの時間を少なくする。

2 検証環境

2.1 版数の確認

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

2.2 搭載CPU数の確認

仮想マシンはCPUを4個搭載しています。なお、lscpuコマンドの使い方は、lscpuコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 1:1:1:1          yes
  2    0      2    2 2:2:2:2          yes
  3    0      3    3 3:3:3:3          yes

kvmホストはCPUを8個搭載しています。

[root@kvm ~]# lscpu -xe
CPU NODE SOCKET CORE L1d:L1i:L2:L3 ONLINE
  0    0      0    0 0:0:0:0          yes
  1    0      1    1 2:2:2:2          yes
  2    0      2    2 4:4:4:4          yes
  3    0      3    3 6:6:6:6          yes
  4    0      4    4 8:8:8:8          yes
  5    0      5    5 10:10:10:10      yes
  6    0      6    6 12:12:12:12      yes
  7    0      7    7 14:14:14:14      yes

3 実験1

以下の方針で検証環境を作成します。
1.アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。vCPUと物理CPUを固定的に割り当てることができれば、どのように割り当ててもかまいません。
2.CPU負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

3.1 事前準備

virsh vcpuinfoコマンドを使用すると、vCPU0が物理CPU6に割り当てられていることがわかります。しかし、CPUアフィニティが物理CPU0から物理CPU7に設定されているので、物理CPUの負荷状況によっては、vCPU0が物理CPU6以外に割り当てられる可能性があります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     33.6s
CPU アフィニティー: 0-7/8
-snip

そこで、vCPU0を物理CPU6に固定的に割り当てるため、virsh vcpupinコマンドを使用してvCPU0を物理CPU6に固定します。

[root@kvm ~]# virsh vcpupin 01_server_alma92 --vcpu 0 6

仮想マシンを再起動します。

[root@kvm ~]# virsh reboot 01_server_alma92
ドメイン '01_server_alma92' は再起動されています

virsh vcpuinfoコマンドを実行すると、CPU アフィニティーが物理CPU6となっているので、vCPU0が物理CPU6に固定的に割り当てられていることがわかります。

[root@kvm ~]# virsh vcpuinfo --pretty 01_server_alma92
VCPU:           0
CPU:            6
状態:         実行中
CPU 時間:     58.4s
CPU アフィニティー: 6/8
-snip-

3.2 動作検証(kvmホストのCPUに負荷をかける)

stress-ngコマンドを以下の条件で実行します。なお、stress-ngコマンドの使い方は、stress-ngコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。
・物理CPU6でCPU使用率90%のstress-ngプロセスを実行する。
・stress-ngプロセスは、ラウンドロビン(RR)のスケジューリングポリシーでリアルタイムプロセスとして実行する。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &

psコマンドでstress-ngプロセスの状態を確認します。物理CPU6でCPU使用率90%程度で動作していることがわかります。また。ラウンドロビン(RR)のスケジューリングポリシーで優先度99のリアルタイムプロセスとして動作していることがわかります。なお、psコマンドの使い方は、psコマンドの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@kvm ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          2476    2209   6  0.0  RR   -     99
stress-ng          2477    2476   6 89.0  RR   -     99

仮想マシンでsarマンドを実行すると、CPU全体(all)の「%steal」が約7%、vCPU0の「%steal」が22%であることがわかります。

[root@server ~]# sar -P ALL 1
22時01分16秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
22時01分17秒     all      0.00      0.00      0.00      0.00      6.76     93.24
22時01分17秒       0      0.00      0.00      0.00      0.00     22.48     77.52
22時01分17秒       1      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       2      0.00      0.00      0.00      0.00      0.00    100.00
22時01分17秒       3      0.00      0.00      0.00      0.00      0.00    100.00
-snip-

さらに、ターミナルを開いてvmstatコマンドを実行してみます。sarコマンドとvmstaコマンドを同時に実行して結果を見比べると、vmstatコマンドの「st」列はsarコマンドのCPU全体の「%steal」に相当することがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3379184         1636       185656    0    0    21     1   23   22   0   0  94   0   5
   0    0            0      3379184         1636       185656    0    0     0     0   73   92   0   0  93   0   7
   0    0            0      3379184         1636       185656    0    0     0     0   56   75   0   0  89   0  11
-snip-

3.3 動作検証(仮想マシンのCPUに負荷をかける)

さらに、kvmホストでもstress-ngプロセスを実行します。kvmホストで優先度の高いstress-ngプロセスを実行すると、仮想マシンで実行するstress-ngプロセスが実行されにくくなります。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &

psコマンドを使用して、stress-ngプロセスの状態を確認します。stress-ngプロセスをCPU使用率90%で起動していますが、仮想マシンで実行しているプロセスよりも優先度の高いプロセスがkvmホストで実行されているので、vCPU0のCPU使用率が約7%であることが確認できます。また、このプロセスがタイムシェアリングプロセス(TS)として実行されていることも確認できます。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%cpu,cls,ni,rtprio
COMMAND             PID    PPID PSR %CPU CLS  NI RTPRIO
stress-ng          1303    1271   0  0.0  TS   0      -
stress-ng          1304    1303   0  7.4  TS   0      -

sarコマンドを実行します。vCPUでstress-ngプロセスを実行すると、vCPUが物理CPUに割り当てられる割合が少なくなるので、「%steal」の値が大きくなることがわかります。

[root@server ~]# sar -P ALL 1
平均値:      CPU     %user     %nice   %system   %iowait    %steal     %idle
平均値:      all      2.26      0.00      0.25      0.00     22.56     74.94
平均値:        0      9.15      0.00      0.34      0.00     90.51      0.00
平均値:        1      0.00      0.00      0.33      0.00      0.00     99.67
平均値:        2      0.00      0.00      0.33      0.00      0.66     99.01
平均値:        3      0.00      0.00      0.00      0.00      0.33     99.67

vmstat コマンドを実行します。「st」列を確認すると、22%程度(全vCPUの25%)になっていることがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   1    0            0      3330928         1636       208972    0    0     9     0   30   20   1   0  86   0  13
   1    0            0      3330928         1636       208972    0    0     0     0  287   95   2   0  76   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  151   68   2   0  75   0  22
   1    0            0      3330928         1636       208972    0    0     0     0  337  138   2   0  76   0  22

4 実験2

以下の方針で検証環境を作成します。
1. アフィニティ
・vCPU0を物理CPU6に固定的に割り当てる。
・vCPU1を物理CPU7に固定的に割り当てる。
2. 物理CPUの負荷
・物理CPU6でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
・物理CPU7でCPU使用率90%のリアルタイムプロセス(優先度99)を実行する。
3. 仮想CPUの負荷
・vCPU0でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。
・vCPU1でCPU使用率90%のタイムシェアリングプロセス(優先度120)を実行する。

仮想マシンでstress-ngコマンドを実行します。

[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 0 -q &
[root@server ~]# stress-ng -k -l 90 -c 1 --taskset 1 -q &

kvmホストでstress-ngコマンドを実行します。

[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 6 --sched rr --sched-prio 99 -q &
[root@kvm ~]# stress-ng -k -l 90 -c 1 --taskset 7 --sched rr --sched-prio 99 -q &

sarコマンドを実行すると、vCPU0とvCPU1の「%steal 」の値が約90%となっていて、vCPU0とvCPU1は物理CPUにほとんど割り当てられていないことがわかります。また、allの「%steal 」は約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# sar -P ALL 1
Linux 5.14.0-284.11.1.el9_2.x86_64 (server)     2023年12月02日  _x86_64_        (4 CPU)

20時50分55秒     CPU     %user     %nice   %system   %iowait    %steal     %idle
20時50分56秒     all      4.31      0.00      0.25      0.00     44.92     50.51
20時50分56秒       0      8.25      0.00      0.00      0.00     91.75      0.00
20時50分56秒       1      9.28      0.00      0.00      0.00     90.72      0.00
20時50分56秒       2      0.00      0.00      0.99      0.00      0.00     99.01
20時50分56秒       3      0.00      0.00      0.00      0.00      0.00    100.00

vmstatコマンドを実行します。「st」列の値が約45%になっていて、仮想マシンのvCPUの約半分が物理CPUに割り当てられていないことがわかります。

[root@server ~]# vmstat 1 -w
--procs-- -----------------------memory---------------------- ---swap-- -----io---- -system-- --------cpu--------
   r    b         swpd         free         buff        cache   si   so    bi    bo   in   cs  us  sy  id  wa  st
   2    0            0      3361036         1636       193308    0    0    23     1   28   25   0   1  94   0   6
   3    0            0      3361036         1636       193308    0    0     0     0  527  194   4   0  51   0  44
   2    0            0      3361036         1636       193308    0    0     0     0  360  137   5   1  50   0  45
   2    0            0      3361036         1636       193308    0    0     0     0  395  153   5   0  50   0  45
-snip-

5 まとめ

・「%steal」項目は、vCPUが物理CPUに割り当てられなかった割合を表しています。
・vCPUでプロセスを実行していない場合より、vCPUでプロセスを実行している方が、「%steal」の値は大きくなります。

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

gpgコマンドの使い方

1 gpgコマンドとは?

gpgコマンドは、ファイルの暗号化・復号や署名の作成・検証等を行うコマンドです。

2 検証環境

2.1 ネットワーク構成

gpgコマンドの動作確認をするため、2台の仮想マシン(server ,client)を使用します。そして、X-sanがserver、Y-sanがclientで、それぞれ鍵ペア(公開鍵と秘密鍵)を作成します。

                 192.168.122.0/24
server ------------------------------------- client
      .14                                .87

本記事では、以下のことを確認してみます。
・ファイルの暗号化・復号
・ファイルに署名を作成・署名の検証

2.2 版数

仮想マシンのAlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

仮想マシンカーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

私の環境ではpinentryパッケージのインストールが必要だったので、仮想マシンにpinentryパッケージをインストールしました。

[root@server ~]# dnf install pinentry

gpgの版数は以下のとおりです。

[root@server ~]# gpg --version
gpg (GnuPG) 2.3.3
-snip

3 オプション一覧

gpgコマンドのオプションは以下のとおりです。

[root@server ~]# gpg --help
gpg (GnuPG) 2.3.3
libgcrypt 1.10.0-unknown
Copyright (C) 2021 Free Software Foundation, Inc.
License GNU GPL-3.0-or-later <https://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Home: /root/.gnupg
サポートしているアルゴリズム:
公開鍵: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
暗号方式: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256,
      TWOFISH, CAMELLIA128, CAMELLIA192, CAMELLIA256
AEAD: EAX, OCB
ハッシュ: SHA1, RIPEMD160, SHA256, SHA384, SHA512, SHA224
圧縮: 無圧縮, ZIP, ZLIB, BZIP2

形式: gpg [オプション] [ファイル]
署名、検査、暗号化または復号
デフォルトの操作は、入力データに依存

コマンド:

 -s, --sign                         署名を作成
     --clear-sign                   クリア・テクスト署名を作成
 -b, --detach-sign                  分遣署名を作成
 -e, --encrypt                      データを暗号化
 -c, --symmetric                    暗号化には共通鍵暗号方式のみを使用
 -d, --decrypt                      データを復号 (デフォルト)
     --verify                       署名を検証
 -k, --list-keys                    鍵の一覧
     --list-signatures              鍵と署名の一覧
     --check-signatures             鍵署名の検査と一覧
     --fingerprint                  鍵とフィンガープリントの一覧
 -K, --list-secret-keys             秘密鍵の一覧
     --generate-key                 新しい鍵ペアを生成
     --quick-generate-key           すばやく新しい鍵ペアを生成
     --quick-add-uid                すばやく新しいユーザIDを追加
     --quick-revoke-uid             すばやくユーザIDを失効
     --quick-set-expire             すばやく新しい有効期限を設定
     --full-generate-key            全機能の鍵ペアを生成
     --generate-revocation          失効証明書を生成
     --delete-keys                  公開鍵リングから鍵を削除
     --delete-secret-keys           秘密鍵リングから鍵を削除
     --quick-sign-key               すばやく鍵に署名
     --quick-lsign-key              すばやく鍵へローカルに署名
     --quick-revoke-sig             すばやく鍵への署名を失効
     --sign-key                     鍵に署名
     --lsign-key                    鍵へローカルに署名
     --edit-key                     鍵への署名や編集
     --change-passphrase            パスフレーズの変更
     --export                       鍵をエクスポートする
     --send-keys                    鍵サーバに鍵をエクスポートする
     --receive-keys                 鍵サーバから鍵をインポートする
     --search-keys                  鍵サーバの鍵を検索する
     --refresh-keys                 鍵サーバから鍵を全部更新する
     --import                       鍵のインポート/マージ
     --card-status                  カード・ステイタスを表示
     --edit-card                    カードのデータを変更
     --change-pin                   カードのPINを変更
     --update-trustdb               信用データベースを更新
     --print-md                     メッセージ・ダイジェストを表示
     --server                       サーバ・モードで実行
     --tofu-policy VALUE            TOFUポリシーを鍵に設定する

診断出力を制御するオプション:
 -v, --verbose                      冗長
 -q, --quiet                        いくらかおとなしく
     --options FILE                 FILEからオプションを読み込みます
     --log-file FILE                FILEにサーバ・モードのログを書き出す

コンフィグレーションを制御するオプション:
     --default-key NAME             デフォルトの秘密鍵としてNAMEを用いる
     --encrypt-to NAME              ユーザID NAMEにも暗号化する
     --group SPEC                   電子メールエイリアスを設定する
     --openpgp                      厳密なOpenPGPの振舞を採用
 -n, --dry-run                      無変更
 -i, --interactive                  上書き前に確認

出力を制御するオプション:
 -a, --armor                        ASCII形式の外装を作成
 -o, --output FILE                  出力をFILEに書き出す
     --textmode                     正準テキスト・モードを使用
 -z N                               圧縮レベルをNに設定 (0は非圧縮)

鍵のインポートとエクスポートを制御するオプション:
     --auto-key-locate MECHANISMS   メールアドレスによって鍵を特定する際、MECHANISMSを使用する
     --auto-key-import              署名から手元にない鍵をインポートする
     --include-key-block            署名に公開鍵を含める
     --disable-dirmngr              dirmngrへのすべてのアクセスを無効とする

鍵を指定するオプション:
 -r, --recipient USER-ID            USER-ID用に暗号化
 -u, --local-user USER-ID           署名や復号にこのUSER-IDを使用

Project-Id-Version: gnupg 2.3.0
Report-Msgid-Bugs-To: translations@gnupg.org
PO-Revision-Date: 2021-04-08 10:15+0900
Last-Translator: NIIBE Yutaka <gniibe@fsij.org>
Language-Team: none
Language: ja
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=1; plural=0;
:

(コマンドとオプション全部の一覧は、マニュアル・ページをご覧ください)

例:

 -se -r Bob [ファイル]      ユーザBobへ署名と暗号化
 --clear-sign [ファイル]     クリア・テクスト署名を作成
 --detach-sign [ファイル]   分遣署名を作成
 --list-keys [名前]         鍵を表示
 --fingerprint [名前]       フィンガープリントを表示

バグは <https://bugs.gnupg.org> までご報告ください。

4 基本的な使い方

実践編の前に、gpgコマンドの基本的な使い方を確認してみます。

4.1 鍵ペアを作成する方法(--gen-key)

--gen-keyオプションは、公開鍵と秘密鍵の鍵ペアを作成するオプションです。

[root@server ~]# gpg --gen-key

「本名」はX-san、「電子メールアドレス」はX-san@hoge.comと入力します。そして、「O」を入力して「Enter」キーを押下します。

秘密鍵が盗まれても、悪用されにくくするため、秘密鍵パスフレーズを設定します。パスフレーズを設定したら、「Tab」キーを押下して「OK」にカーソルをあわせます。そして、「Enter」を押下します。

再度、パスフレーズを入力します。

鍵ペアの作成が完了すると、以下のようになります。

4.2 公開鍵を確認する方法( --list-keys、または-k(小文字))

--list-keysオプションは、公開鍵を表示するオプションです。4.1で作成した公開鍵を確認してみます。公開鍵のidがX-sanであることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.3 秘密鍵を確認する方法(--list-secret-keys、または-K(大文字))

--list-secret-keysオプションは、秘密鍵を表示するオプションです。4.1で作成した秘密鍵を確認してみます。

[root@server ~]# gpg --list-secret-keys
/root/.gnupg/pubring.kbx
------------------------
sec   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      DC4A5479C1E3417346F1AC5C084F72AF5B2AFB76
uid           [  究極  ] X-san <X-san@hoge.com>
ssb   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

4.4 秘密鍵を削除する方法(--delete-secret-keys)

--delete-secret-keysオプションは、秘密鍵を削除するオプションです。鍵の削除は秘密鍵、公開鍵の順で行わないと、gpgコマンドでエラーが出力されるので、まず、X-sanの秘密鍵から削除します。

[root@server ~]# gpg --delete-secret-keys X-san

「鍵を削除するか?」と聞かれるので、「y」を入力します。

「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。

再度、「鍵を削除するか?」と聞かれるので、「Tab」キーを押下して、「鍵を削除する」にカーソルを合わせます。そして、「Enter」キーを押下します。


秘密鍵を確認すると、リストから削除されたことがわかります。

[root@server ~]# gpg --list-secret-keys
[root@server ~]#

4.5 公開鍵を削除する方法(--delete-keys)

--delete-keysオプションは、公開鍵を削除するオプションです。X-sanの公開鍵を削除します。

[root@server ~]# gpg --delete-keys X-san
gpg (GnuPG) 2.3.3; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  rsa3072/084F72AF5B2AFB76 2023-11-27 X-san <X-san@hoge.com>

この鍵を鍵リングから削除しますか? (y/N) y

リストを確認すると、X-sanの公開鍵が削除されたことがわかります。

[root@server ~]# gpg --list-keys
gpg: 信用データベースの検査
gpg: 究極的に信用する鍵が見つかりません

4.6 鍵をエクスポートする方法(--export)

4.4, 4.5でX-sanの公開鍵、秘密鍵を削除したので、4.1の手順にしたがって、X-sanの公開鍵、秘密鍵を作成しておきます。

--exportオプションは、公開鍵をファイルにエクスポート(書き出す)するオプションです。X-sanの公開鍵をエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

公開鍵を確認すると、X-sanの公開鍵がファイルにエクスポートされたことがわかります。

[root@server ~]# ls X-san.pub
X-san.pub

4.7 鍵をインポートする方法(--import)

clientで作成した公開鍵をserverに送信します。そして、公開鍵のリストにインポートしてみます。

--importオプションは、公開鍵をリストに追加するオプションです。clientでY-sanの公開鍵を作成します。

[root@client ~]# gpg --gen-key

clientで作成したY-sanの公開鍵をエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

serverに公開鍵を送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

serverでY-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートします。

[root@server ~]# gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E: 公開鍵"Y-san <Y-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

公開鍵のリストを確認します。X-sanの公開鍵の他に、Y-sanの公開鍵もリストに追加されていることがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

特定の公開鍵だけを表示するには、--list-keysオプションのパラメータにuidを指定します。
たとえば、X-sanだけの公開鍵を表示するには、以下のように実行します。

[root@server ~]# gpg --list-keys X-san
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5 実践編(ファイルを暗号化・復号する方法)

以下の順でファイルを暗号化・復号する方法を確認してみます。
1. serverでY-sanの公開鍵でファイルを暗号化する
2. 暗号化したファイルをserverからclientに送信する
3. clientでY-sanの秘密鍵でファイルを復号する

5.1 事前準備

clientで鍵ペアを作成します。作成した公開鍵を確認します。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

Y-sanの公開鍵をファイルにエクスポートします。

[root@client ~]# gpg -ao Y-san.pub --export Y-san

エクスポートしてファイルをserverに送信します。

[root@client ~]# scp Y-san.pub root@192.168.122.14:/root

エクスポートしたY-sanの公開鍵はいつでもリストから取り出すことができるので、削除します。

[root@client ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

Y-sanの公開鍵を確認します。

[root@server ~]# ls Y-san.pub
Y-san.pub

Y-sanの公開鍵をインポートしてリストに追加します。

[root@server ~]#  gpg --import Y-san.pub
gpg: 鍵F12039A2064B868E:"Y-san <Y-san@hoge.com>"変更なし
gpg:           処理数の合計: 1
gpg:               変更なし: 1

Y-sanの公開鍵をリストに追加したので、Y-sanの公開鍵を削除します。

[root@server ~]# rm Y-san.pub
rm: 通常ファイル 'Y-san.pub' を削除しますか? y

公開鍵を確認すると、X-sanの公開鍵の他に、Y-sanの公開鍵が追加されたことがわかります。

[root@server ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  究極  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  不明  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

5.2 ファイルを暗号化・復号する方法

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

Y-sanの公開鍵でテスト用ファイルを暗号化します。

[root@server ~]# gpg -r Y-san -ea test.txt
gpg: FBA0E21345620F47: この鍵が本当に本人のものである、という兆候が、ありません

sub  rsa3072/FBA0E21345620F47 2023-11-27 Y-san <Y-san@hoge.com>
  主鍵フィンガープリント: 3F60 5381 2566 75AD C76A  2ECD F120 39A2 064B 868E
  副鍵フィンガープリント: 65FF F682 24BA B68B CF42  3169 FBA0 E213 4562 0F47

この鍵は、このユーザIDをなのる本人のものかどうか確信でき
ません。今から行うことを*本当に*理解していない場合には、
次の質問にはnoと答えてください。

それでもこの鍵を使いますか? (y/N) y

暗号化したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP MESSAGE-----

hQGMA/ug4hNFYg9HAQwAo4TLQiL3dvIucsupKIHZ+PxUrrg4fSxErG0R16ZMtB5R
C/vWll1bgPIiHaDpSgvx0iFNdbJN6PXzDsxqIxBy3LaasdmjamYcPIUF0PvXzWmE
VOH2ZMPJtNDQsySQ78oLov/iiRdGX5WjdF7vWm05HxS1JrYaKSblke9FpPJMii39
NiqRA+zKACIjOYrNZVYXDQGk0kiO8OvIAO2HJ2Qa4Y1XGk/T8Mv6aRfOxnqz6owN
ljG/hFDMilgAeiMT1VtUkwfCll9rKEGzjvAGniknkncBKhu+o/BlGA+1x9Oazfei
Dz6T/MGmslqow7xvInSFOdPFIBLPZEWEvu2+3XydECMMxrbXHL/VFVLAN0nuIzNE
H2oGweYQ0G+bBTpSKL2GYadNrXbV6oa6wEp+ZmjfljjAu9UEGHz+0TMUnNI4oXur
3NojbeUc4PC6my+XMICLZSpjvPA8Cg7iqwUoFdEBw29P+XYhK5wGtUu+kwwn2dqg
dqjsiUhZrP9HeGYNzchy1FgBCQIQdIrpYtGYNGCyzVy2AxZyi67PN9QogNlU0uuG
NP1isCnp581T4oLdU+pYyKAUmnq4mfarNx0SsjB/yq7uiccnW9csBrDHX10Hm82j
IZtqu2r0VByy
=YYzY
-----END PGP MESSAGE-----

暗号化したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

clientで暗号化したファイルを確認します。

[root@client ~]# ls test.txt.asc
test.txt.asc

暗号化したファイルをY-sanの秘密鍵で復号します。-oオプションを使用して、復号した結果をtest.txtに出力します。

[root@client ~]# gpg -d -o test.txt test.txt.asc
gpg: rsa3072鍵, ID FBA0E21345620F47, 日付2023-11-27に暗号化されました
      "Y-san <Y-san@hoge.com>

復号したファイルを確認します。正しく復号できていることがわかります。

[root@client ~]# cat test.txt
0123456789

6 実践編(署名を作成・検証する方法)

以下の順で署名の作成・検証する方法を確認してみます。
1. serverでX-sanの秘密鍵でファイルに署名する
2. 署名したファイルをserverからclientに送信する
3. clientでX-sanの公開鍵で署名を検証する

6.1 事前準備

serverでテスト用ファイルを作成します。

[root@server ~]# vi test.txt
[root@server ~]# cat test.txt
0123456789

X-sanの秘密鍵で署名したファイルをclientで検証するには、X-sanの公開鍵をclientに送信しておく必要がありますので、X-sanの公開鍵をファイルにエクスポートします。

[root@server ~]# gpg -ao X-san.pub --export X-san

エクスポートしたファイルをclientに送信します。

[root@server ~]# scp X-san.pub root@192.168.122.87:/root

X-sanの公開鍵を削除します。

[root@server ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

X-sanの公開鍵をインポートします。

[root@client ~]# gpg --import X-san.pub
gpg: 鍵5098AB5CC2CA9B28: 公開鍵"X-san <X-san@hoge.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

X-sanの公開鍵をリストに追加したので、X-sanの公開鍵を削除しておきます。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

公開鍵のリストを確認します。X-sanの公開鍵がリストに追加されてことがわかります。

[root@client ~]# gpg --list-keys
/root/.gnupg/pubring.kbx
------------------------
pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      3F605381256675ADC76A2ECDF12039A2064B868E
uid           [  究極  ] Y-san <Y-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

pub   rsa3072 2023-11-27 [SC] [有効期限: 2025-11-26]
      D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28
uid           [  不明  ] X-san <X-san@hoge.com>
sub   rsa3072 2023-11-27 [E] [有効期限: 2025-11-26]

X-sanの公開鍵を削除します。

[root@client ~]# rm X-san.pub
rm: 通常ファイル 'X-san.pub' を削除しますか? y

6.2 署名の作成・検証する方法

X-sanの秘密鍵でテスト用ファイルに署名をします。

[root@server ~]# gpg --clearsign test.txt

署名したファイルを確認します。

[root@server ~]# cat test.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

0123456789
-----BEGIN PGP SIGNATURE-----

iQGzBAEBCAAdFiEE2KxmWPwitkytRtwAUJirXMLKmygFAmVkdNQACgkQUJirXMLK
myi9rgv+OXcaDKzYzB1zMuLeeHiKp+8Cxvx7dNcA57Pso0awgiQlYdRZjJKrbPki
ymasPetS5Y9AQlFfY/Hd3BSw+rd4TI+IwjDBcoCxEI5OrEWvonSVWxD0cZnuy7Ux
1DlzR58xEwlWafA433EKk7i1FmWFeObTMhZjIA0z32d8Asu+XBNZtvDhbB1y4F+f
w26bZVSfYunf3TUICOWNOvvmBrH+RJSuZYuKUJ3Swm+3jcJUyj8OX05pmexqWRwC
5vjCyrVk/KOhm4WGawC+eT1HFiB7/AlUWHOuE/f6mKK0WLXF9Wnya4wlZHGyIAGk
nynAoJSVl0zA+LnHWDs941KN/W7mKNU58XgxfDAC8I4GX5BlzguETCMS3/K8s/dD
bpO8pMsVLPae+InEshDPbnGlRJeQjVhmfB0fz+ChfsdDLaJmC4DQb/Sl7/sHFtU9
FalEKweOJUA1b+rgr1iwYZk2hx8yavie76s3mFqvEbqQPjmd/HWr0c73AsU+kPuP
9tjRCpBG
=tynM
-----END PGP SIGNATURE-----

署名したファイルをclientに送信します。

[root@server ~]# scp test.txt.asc root@192.168.122.87:/root

署名を検証します。X-sanが署名したことがわかります。

[root@client ~]# gpg --verify test.txt.asc
gpg: 2023年11月27日 19時52分04秒 JSTに施された署名
gpg:                RSA鍵D8AC6658FC22B64CAD46DC005098AB5CC2CA9B28を使用
gpg: "X-san <X-san@hoge.com>"からの正しい署名 [不明の]
gpg: *警告*: この鍵は信用できる署名で証明されていません!
gpg:       この署名が所有者のものかどうかの検証手段がありません。
 主鍵フィンガープリント: D8AC 6658 FC22 B64C AD46  DC00 5098 AB5C C2CA 9B28
gpg: *警告*: 分遣署名ではありません。ファイル「test.txt」は検証され*ませんでした*!

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ

GPGによる公開鍵暗号と署名

Matplotlibの使い方

1 Matplotlibとは?

Matplotlibはグラフを描画するためのライブラリです。

2 検証環境

AlmaLinux版数は以下のとおりです。

[root@server ~]# cat /etc/redhat-release
AlmaLinux release 9.2 (Turquoise Kodkod)

カーネル版数は以下のとおりです。

[root@server ~]# uname -r
5.14.0-284.11.1.el9_2.x86_64

pythonの版数は以下のとおりです。

[root@server ~]# python -V
Python 3.9.16

3 Matplotlibのインストール方法

グラフを描画するため、Matplotlibライブラリをインストールします。

[root@server ~]# pip install matplotlib

Matplotlibの版数を確認します。

[root@server ~]# pip show matplotlib
Name: matplotlib
Version: 3.8.0
Summary: Python plotting package
Home-page: https://matplotlib.org
Author: John D. Hunter, Michael Droettboom
Author-email: matplotlib-users@python.org
License: PSF
Location: /usr/local/lib64/python3.9/site-packages
Requires: contourpy, cycler, fonttools, importlib-resources, kiwisolver, numpy, packaging, pillow, pyparsing, python-dateutil
Required-by:

Matplotlibで描画するグラフに日本語のタイトル等を表示するため、japanize-matplotlibをインストールします

[root@server ~]# pip install japanize-matplotlib

japanize-matplotlibの版数を確認します。

[root@server ~]# pip show japanize-matplotlib
Name: japanize-matplotlib
Version: 1.1.3
Summary: matplotlibのフォント設定を自動で日本語化する
Home-page: https://github.com/uehara1414/japanize-matplotlib
Author: uehara1414
Author-email: akiya.noface@gmail.com
License: MIT License
Location: /usr/local/lib/python3.9/site-packages
Requires: matplotlib
Required-by:

4 DataFrameからグラフを作成する方法

4.1 グラフの種類

種類 概要
折れ線グラフ 変化をみるときに使う
積み上げグラフ 変化の要因を知りたいときに使う
面グラフ 変化の大きさを強調してみるときに使う
箱ひげグラフ データの散らばり具合を見るときに使う

4.2 テスト用ファイルの作成

テストプログラムが読み込む、テスト用ファイルを作成します。

[root@server ~]# cat test.csv
名前,国語,数学,英語
佐藤,90,50,80
鈴木,50,100,80
田中,70,60,80
伊藤,40,60,100

4.3 折れ線グラフを作成する方法(plot)

plotメソッドは、折れ線グラフを作成するためのメソッドです。

折れ線グラフを作成するテストプログラムを作成します。importステートメントの意味は以下のとおりです。
・matplotlibライブラリからグラフ描画(pyplot)に関する機能をpltとしてインポートする。
・日本語タイトルなどを描画するために使用するjapanize_matplotlibをインポートする。
・データ分析のために使用するpandasライブラリをpdとしてインポートする。
なお、pandasの使い方は、pandasの使い方 - hana_shinのLinux技術ブログを参照してください。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 折れ線グラフを作成
df.plot()

# グラフを表示
plt.show()

テストプログラムを実行すると、テスト用ファイルのデータが折れ線グラフで表示されていることがわかります。なお、本記事では、グラフを表示するために、MobaXtermを使用しています。MobaXtermの使い方は、MobaXtermの使い方 - hana_shinのLinux技術ブログを参照してください。

4.4 棒グラフを作成する方法(plot.bar)

棒グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# グラフを表示
plt.show()

テストプログラムを実行すると、棒グラフが表示されていることがわかります。

4.5 積み上げグラフを作成する方法(plot.bar(stacked=true))

積み上げグラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 積み上げグラフを作成
df.plot.bar(stacked=True)

# グラフを表示
plt.show()

テストプログラムを実行すると、積み上げグラフが表示されていることがわかります。

4.6 面グラフを作成する方法(plot.area)

面グラフを作成するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 面グラフを作成
df.plot.area()

# グラフを表示
plt.show()

テストプログラムを実行すると、面グラフが表示されていることがわかります。

4.7 箱ひげグラフを作成する方法(plot.box)

[root@server ~]# cat test.py
#!/usr/bin/python3
import matplotlib.pyplot as plt
import japanize_matplotlib
import pandas as pd

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 箱ひげグラフを作成
df.plot.box()

# グラフを表示
plt.show()

テストプログラムを実行すると、箱ひげグラフが表示されていることがわかります。

5 グラフに汎用要素を設定する方法

5.1 タイトル、ラベルを表示する方法

4.4で作成した棒グラフに、タイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# 棒グラフを作成
df.plot.bar()

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、グラフのタイトル(成績グラフ)、X軸(名前)、Y軸(点数)のラベルが表示されていることがわかります。

5.2 X軸のインデックスを表示する方法

X軸のインデックスに名前を表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グラフを表示
plt.show()

テストプログラムを実行すると、X軸に名前が表示されていることがわかります。

5.3 グリッドをオンにする方法

グリッドを表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv")

# DataFrameの名前列をインデックスに設定
df = df.set_index('名前')

# 棒グラフを作成
df.plot.bar(rot=0)

# タイトル、ラベルの設定
plt.title('成績グラフ')
plt.xlabel('名前')
plt.ylabel('点数')

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、グリッドが表示されていることがわかります。

6 個別のデータをグラフで表示する方法

これまでは読み込んだ全てのデータをグラフに表示していました。ここでは、特定の項目(例えば国語)だけのグラフの表示や、ある人物(例えば佐藤さん)だけのグラフを表示してみます。

6.1 特定の項目(数学と国語)のグラフを表示する方法

各個人の数学と国語に関する点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df[["国語","数学"]].plot.barh()

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、各個人の数学、国語の点数が表示されていることがわかります。

6.2 特定の項目(佐藤さん)の棒グラフを表示する方法

佐藤さんの英語、数学、国語の点数を棒グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 棒グラフを作成
df.loc["佐藤"].plot.barh()

# タイトルを追加
plt.title("佐藤さんの成績")

# グリッドをオンにする
plt.grid(True)

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が棒グラフで表示されていることがわかります。

6.3 特定の項目(佐藤さん)の円グラフを表示する方法

佐藤さんの英語、数学、国語の点数を円グラフで表示するテストプログラムを作成します。

[root@server ~]# cat test.py
#!/usr/bin/python3
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib

# csvファイルを読み込む
df = pd.read_csv("test.csv", index_col=0)

# 円グラフを作成する
df.loc["佐藤"].plot.pie()

# グラフを表示
plt.show()

テストプログラムを実行すると、佐藤さんの英語、数学、国語の点数が円グラフで表示されていることがわかります

Y 参考図書


TECHNICAL MASTER はじめてのAlmaLinux 9 & Rocky Linux 9 Linuxサーバエンジニア入門編

TECHNICAL MASTER はじめてのAlmaLinux 9 & Rocky Linux 9 Linuxサーバエンジニア入門編 (TECHNICAL MASTER 100) 単行本(ソフトカバー)




Rocky Linux & AlmaLinux実践ガイド impress top gearシリーズ

Rocky Linux & AlmaLinux実践ガイド (impress top gear) 単行本(ソフトカバー)

サンプルコードが短く、分かりやすいです。
Pythonコードレシピ集(単行本)


Pythonコードレシピ集(Kindle版)

  • スッキリわかるPython入門 (スッキリわかる入門シリーズ)

簡潔な説明で分かりやすいです。

スッキリわかるPython入門 (スッキリわかる入門シリーズ)(単行本)

スッキリわかるPython入門 (スッキリわかるシリーズ)(Kindle版)

Webのスクレイピングについて簡潔で分かりやすく説明されています。

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(単行本)

Python2年生 スクレイピングのしくみ 体験してわかる!会話でまなべる!(Kindle版)

  • Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ


Python1年生 第2版 体験してわかる!会話でまなべる!プログラミングのしくみ(単行本)

Python 1年生 体験してわかる!会話でまなべる!プログラミングのしくみ(Kindle版)

Z 参考情報

私が業務や記事執筆で参考にした書籍を以下のページに記載します。
Linux技術のスキルアップをしよう! - hana_shinのLinux技術ブログ