2022年10月29日土曜日

GitLabをActive Directoryと認証連携する手順

先日自宅にGitLabを導入し、AnsibleやPythonのコードなどの管理を実施している。

GitLabはGitLab自身にてユーザやグループを管理する機能を持っており、自宅で利用する分には、その機能で十分対応できる。しかし、多数のユーザでGitLabを共同利用するようなケースでは、ユーザ登録、削除、更新などの管理が煩雑になってしまう問題がある。

このような場合は、別にユーザ認証を行うActive Directory(以下、AD)やLDAPのサーバと認証連携を行うと管理が楽になる。今回はGitLabとActive Directoryを認証連携するための手順を記載する。

環境

以下、導入OSとGitLabのバージョンを記載する。

  • OS : AlmaLinux 8.5
  • GitLab : 15.1.2

ADとの連携は、LDAP(ポート番号389)または、LDAPS(ポート番号636)のプロトコルを用いる。もしLDAPSを用いる場合は、ADにLDAPS用の証明書をインストールしLDAPSを有効化することが必要となる。ADのLDAPS有効化手順は、以下記事を参照いただきたい。

GitLabでADと認証連携する手順

1. 設定ファイルをバックアップ

GitLabの設定ファイルは/etc/gitlab/gitlab.rbとなる。設定変更前にバックアップを取っておこう。
※YYYYMMDDは日付などをいれること。

# cp /etc/gitlab/gitlab.rb{,_YYYYMMDD}

2. 設定個所を確認

設定変更箇所はLDAP Settings以降の個所となる。viなどで設定ファイルを開き、/LDAPで検索を行い該当箇所に移動しよう。

# vi /etc/gitlab/gitlab.rb

~(中略)~

### LDAP Settings
###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html
###! **Be careful not to break the indentation in the ldap_servers block. It is

# gitlab_rails['ldap_enabled'] = false
# gitlab_rails['prevent_ldap_sign_in'] = false

###! **remember to close this block with 'EOS' below**
# gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
#   main: # 'main' is the GitLab 'provider ID' of this LDAP server
#     label: 'LDAP'
#     host: '_your_ldap_server'
#     port: 389
#     uid: 'sAMAccountName'

~(中略)~

# EOS

3. AD連携の設定を追加

ADとの認証連携に必要な設定項目について、以下表にまとめた。

設定項目 設定値
label ログイン画面に表示させる認証方式のタブのラベルとなる。特に好みがなければデフォルトの「LDAP」のままでも問題ないだろう。
gitlab_rails[‘ldap_enabled’] trueに設定する。
host ADのIPアドレスを設定する。
port LDAPの場合は389、LDAPSの場合は636を設定する。
uid ADと認証連携する場合はsAMAccountNameを設定する。
bind_dn AD上のバインドユーザを設定する。
password バインドユーザのパスワードを設定する。
verify_certificates LDAPの場合はfalse、LDAPSの場合はtrueに設定する。
active_directory ADと認証連携する場合はtrueを設定する。
base ベースDNを設定する。
ca_file LDAPSの場合は、ADのSSLサーバ証明書の公開鍵のファイルを/etc/gitlab/trusted-certsディレクトリに配置したうえで、その証明書ファイルのフルパスを設定する。

実際の設定例を以下に記載する。

LDAPの場合

gitlab_rails['ldap_enabled']trueに変更しつつ、

### LDAP Settings
###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html
###! **Be careful not to break the indentation in the ldap_servers block. It is
###!   in yaml format and the spaces must be retained. Using tabs will not work.**

gitlab_rails['ldap_enabled'] = true
gitlab_rails['prevent_ldap_sign_in'] = false

###! **remember to close this block with 'EOS' below**
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
  main:
    label: 'LDAP'
    host: '192.168.1.1'
    port: 389
    uid: 'sAMAccountName'
    bind_dn: 'CN=binduser,OU=My OU,DC=test,DC=local'
    password: 'BINDUSER_PASSWORD'
    encryption: 'plain'
    verify_certificates: false
    smartcard_auth: false
    active_directory: true
    allow_username_or_email_login: true
    lowercase_usernames: false
    block_auto_created_users: false
    base: 'OU=My OU,DC=test,DC=local'
    user_filter: ''
EOS

LDAPSの場合

LDAPの差異がある個所に★印でコメントを記載している。なお、前述したとおり事前にADのSSLサーバ証明書の公開鍵のファイルを/etc/gitlab/trusted-certsディレクトリに配置しておくこと。

### LDAP Settings
###! Docs: https://docs.gitlab.com/omnibus/settings/ldap.html
###! **Be careful not to break the indentation in the ldap_servers block. It is
###!   in yaml format and the spaces must be retained. Using tabs will not work.**

gitlab_rails['ldap_enabled'] = true
gitlab_rails['prevent_ldap_sign_in'] = false

###! **remember to close this block with 'EOS' below**
gitlab_rails['ldap_servers'] = YAML.load <<-'EOS'
  main: # 'main' is the GitLab 'provider ID' of this LDAP server
    label: 'LDAP'
    host: 'ad01.test.local' ★ADのFQDNに変更
    port: 636 ★636に変更
    uid: 'sAMAccountName'
    bind_dn: 'CN=binduser,OU=My OU,DC=test,DC=local'
    password: 'BINDUSER_PASSWORD'
    encryption: 'simple_tls' ★simple_tlsに変更
    verify_certificates: true ★trueに変更
    smartcard_auth: false
    active_directory: true
    allow_username_or_email_login: true
    lowercase_usernames: false
    block_auto_created_users: false
    base: 'OU=My OU,DC=test,DC=local'
    user_filter: ''
    ca_file: '/etc/gitlab/trusted-certs/public_ad.crt' ★追加
EOS

4. 設定を反映

設定を反映するため、gitlab-ctl reconfigureを実行する。環境にもよるが、1分程度で処理は終了する。

# gitlab-ctl reconfigure
[2022-07-16T16:47:27+09:00] INFO: Started Chef Infra Zero at chefzero://localhost:1 with repository at /opt/gitlab/embedded (One version per cookbook)
Chef Infra Client, version 17.10.0
Patents: https://www.chef.io/patents
Infra Phase starting
[2022-07-16T16:47:27+09:00] INFO: *** Chef Infra Client 17.10.0 ***
[2022-07-16T16:47:27+09:00] INFO: Platform: x86_64-linux

~(中略)~

Running handlers:
[2022-07-16T16:47:58+09:00] INFO: Running report handlers
Running handlers complete
[2022-07-16T16:47:59+09:00] INFO: Report handlers complete
Infra Phase complete, 8/857 resources updated in 31 seconds

gitlab Reconfigured!

5. ログイン確認

設定反映後、GitLabのログイン画面にアクセスしてみよう。問題なく設定できていれば、「LDAP」と「Standard」のタブが表示されているはずだ。

「LDAP」タブを選んだ状態でユーザ名(ドメイン名の指定は不要)とパスワードを入力しログインしてみよう。問題なくログインできれば、正常にAD認証連携ができている。

6. 権限付与

一度ADのユーザでGitLabにログインすると、ログインしたユーザが選択できるようになるので、管理者権限を持つGitLabユーザでログインしなおして、該当ユーザに対して必要な権限を付与しよう。

以上で、GitLabとActive Directoryを認証連携するための手順は完了となる。

参考

2022年10月22日土曜日

Ansibleを使ってcronを設定する

自宅ではAlmaLinux上にAnsibleコントロールノードを構築し、各種処理の自動化・コード化を進めている。

さらにAnsibleを使って、GitLabのバックアップやdnfを用いたパッケージ更新を定期的にcronで実行しているが、そもそもcronの設定自体もAnsibleでできるのでは?と思い実際にPlaybookを作ってみた。

今回はAnsibleを使ってcronを設定する手順を記載する。

環境

今回手順を確認した環境のOSは以下の通り。

  • コントロールノード
    • OS : AlmaLinux 8.5
    • Ansible : ansible [core 2.13.1]
  • Ansible実行対象サーバ
    • OS : AlmaLinux 8.5

Ansibleコントロールノード構築やLinuxサーバの事前準備の手順は、以下記事を参照いただきたい。

Ansibleを使ってcronを設定する手順

1. cronジョブ

今回設定するcronジョブは以下2つとした。各ジョブはAnsibleのPlaybookを実行する処理となる。

処理内容 実行時間
GitLabバックアップ 毎日1:30
サーバのパッケージ更新 毎週土曜日2:00

2. Playbook

cronの設定はansible.builtin.cronモジュールを用いる。通常、cronには「ジョブの名前」という概念は存在しないが、本モジュールを利用する場合は、nameによる名前の設定が必須となる。nameで設定したジョブ名は、cronの設定ファイルのコメントとして記載される。

Playbookの各タスクの処理内容を以下に記載する。

タスク名 説明
Setup cron job (Backup gitlab) GitLabをバックアップするcronジョブを設定する。
Setup cron job (Update all package for testserver) サーバのパッケージ更新を行うcronジョブを設定する。
Get crontab 設定したcrontabの情報をcrontab -lコマンドで取得する。
Show crontab コマンド実行結果を表示する。

以下に実際のPlaybookを記載する。cronの記載方法はいつもうろ覚えになるので、あえてコメントで記載ルールを書いておいた。

---
- name: Setup cron job
  gather_facts: false
  hosts: t1025alma

  vars:
    ansible_ssh_user: ansibleuser

    ansible:
      cmd: /usr/local/bin/ansible-playbook
      hosts: /root/jobs/hosts
      playbook_path: /root/jobs
    
  tasks:
    # Example of job definition:
    # .---------------- minute (0 - 59)
    # |  .------------- hour (0 - 23)
    # |  |  .---------- day of month (1 - 31)
    # |  |  |  .------- month (1 - 12) OR jan,feb,mar,apr ...
    # |  |  |  |  .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
    # |  |  |  |  |
    # *  *  *  *  * user-name  command to be executed

    # cronの設定ファイルは/var/spool/cron/[Username]に存在
    - name: Setup cron job (Backup gitlab)
      ansible.builtin.cron:
        name: Backup gitlab
        minute: '30'
        hour: '1'
        day: '*'
        month: '*'
        weekday: '*'
        job: "{{ ansible.cmd }} -i {{ ansible.hosts }} {{ ansible.playbook_path }}/backup_gitlab.yml"
        user: root
        state: present
      become: true

    - name: Setup cron job (Update all package for testserver)
      ansible.builtin.cron:
        name: Update all package for t3036vpns
        minute: '0'
        hour: '2'
        day: '*'
        month: '*'
        weekday: '6'
        job: "{{ ansible.cmd }} -i {{ ansible.hosts }} -l testserver {{ ansible.playbook_path }}/update_linux_dnf.yml"
        user: root
        state: present
      become: true

    # crontabの表示
    - name: Get crontab
      ansible.builtin.command: crontab -l
      register: result
      changed_when: false
      become: true

    - name: Show crontab
      ansible.builtin.debug:
        msg: "{{ result.stdout_lines }}"

3. Playbook実行結果

実際にPlaybookを実行した結果を以下に記載する。Show crontabのタスクの出力結果より、nameで設定したcronジョブ名がコメントとして記載されていることがわかる。

# ansible-playbook -i hosts linux/setup_cron_job.yml 

PLAY [Setup cron job] ***********************************************************************************************

TASK [Setup cron job (Backup gitlab)] *******************************************************************************
changed: [t1025alma]

TASK [Setup cron job (Update all package for t3036vpns)] ************************************************************
changed: [t1025alma]

TASK [Get crontab] **************************************************************************************************
ok: [t1025alma]

TASK [Show crontab] *************************************************************************************************
ok: [t1025alma] => {
    "msg": [
        "#Ansible: Backup gitlab",
        "30 1 * * * /usr/local/bin/ansible-playbook -i /root/jobs/hosts /root/jobs/backup_gitlab.yml",
        "#Ansible: Update all package for t3036vpns",
        "0 2 * * 6 /usr/local/bin/ansible-playbook -i /root/jobs/hosts -l t3036vpns /root/jobs/update_linux_dnf.yml"
    ]
}

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

以上で、Ansibleを使ってcronを設定する手順は完了となる。

2022年10月15日土曜日

ESXi 7.0・ESXi 8.0のOS領域を最小サイズでインストールする

ESXi 7.0やESXi 8.0では、ESXi 6.7の頃と比較してOS領域のパーティションが大幅に設計変更が加わっている。先に結論を言うと、ESXi 6.7以前は数GB程度と非常に少ない容量でOSインストールをすることができたが、ESXi 7.0(ESXi 8.0)では最大で128GBがOSインストール領域として使用される

以下に、ESXi 6.7とESXi 7.0(ESXi 8.0)のdfコマンドの結果を比較した結果を記載する。

▼ESXi 6.7
ESXi 6.7では、OS領域であるvfatの領域が4つが作成され、残りの容量はVMFS-6のデータストアが作成されている。

[root@localhost:~] df -h
Filesystem   Size   Used Available Use% Mounted on
VMFS-6      32.5G   1.4G     31.1G   4% /vmfs/volumes/datastore1
vfat       249.7M 147.6M    102.1M  59% /vmfs/volumes/f471577b-dca3afaf-29c3-0d637cde3004
vfat       285.8M 195.8M     90.0M  68% /vmfs/volumes/60022d77-6ee455cf-0106-000c29369e16
vfat       249.7M   4.0K    249.7M   0% /vmfs/volumes/eb87dc73-7f38d4c9-e1f8-0b2e37658546
vfat         4.0G   4.2M      4.0G   0% /vmfs/volumes/60022d81-569c280c-e4f2-000c29369e16

▼ESXi 7.0(ESXi 8.0)
ESXi 6.7では、OS領域であるvfatの領域が2つが「BOOTBANK1」と「BOOTBANK2」というボリューム名で作成されている。残りの領域は「OSDATA」というボリュームが作成されており、VMFSのデータ領域として利用できる領域は存在しない。

[root@localhost:~] df -h
Filesystem  Size   Used Available Use% Mounted on
VFFS       31.8G   2.8G     29.0G   9% /vmfs/volumes/OSDATA-60039d1b-e99807cf-372a-000c29ff2470
vfat        4.0G 169.3M      3.8G   4% /vmfs/volumes/BOOTBANK1
vfat        4.0G  64.0K      4.0G   0% /vmfs/volumes/BOOTBANK2

上記のようにESXi 7.0(ESXi 8.0)では、インストール対象のディスクサイズが128GB未満の場合は全領域が「OSDATA]というOS領域として確保され、仮想マシン用のデータストアとしては使用できない。検証用途でESXi 7.0を使用する場合、多くの容量がOS領域に使用されるため非常に効率が悪い。

そこで、ESXi 7.0(ESXi 8.0)のインストール時もOSのインストールサイズを縮小し、残りのディスク容量をデータストアとして使用できる手順を確認した。

手順としては以下2つの方法で対処できる。最も容量を減らしたい場合は、2番目のESXi 6.7からのアップグレード手順がおすすめとなる。

  1. ESXi 7.0(ESXi 8.0)のインストーラのブートオプションにautoPartitionOSDataSize=8192を指定
  2. ESXi 6.7インストール後、ESXi 7.0(ESXi 8.0)にアップグレード

以下に、それぞれの手順を記載する。なお、ESXi 6.7からESXi 7.0(ESXi 8.0)にアップグレードする場合は、アップグレードパスをVMwareのサイトで確認しておこう。

環境

環境は以下の通り。本記事の手順は、ESXi 7.0とESXi 8.0両方で使用できるが、手順説明はESXi 7.0を用いて実施する。
  • ESXi 7.0 Update 1c及びESXi 8.0
  • Nested ESXi環境
  • OSインストールディスク容量 : 40GB

ESXi 7.0のインストーラのブートオプションにautoPartitionOSDataSize=8192を指定

1. ESXiのインストーラ起動時に「Shift+O」を押す

「Shift+O」を押すことで、ブートオプションを追加できるようになる。画面表示から5秒以内に押す必要があるので注意しよう。

2. OSData領域のサイズを指定

初めからcdromBoot runweaselと入力されているので、続けて以下のようにautoPartitionOSDataSize=8192のパラメータを追加し、Enterを押下する。

cdromBoot runweasel autoPartitionOSDataSize=8192

3. 通常手順にてインストールを実施

通常の手順にてESXiをインストールする。以前別記事でインストール手順は確認しているので、詳細はそちらを参照いただきたい。

4. インストール後のパーティションの確認

インストール後のディスクパーティションを確認してみると、OSDATA領域は約8GBの容量に抑えられたことにより、残りの容量がVMFS-6のデータストアとして作成されていることがわかる。ただし、40GBのディスクのうち、データストアの容量は23.8GBのみとなった。

[root@localhost:~] df -h
Filesystem  Size   Used Available Use% Mounted on
VMFS-6     23.8G   1.4G     22.3G   6% /vmfs/volumes/datastore1
VFFS        7.8G   2.8G      5.0G  36% /vmfs/volumes/OSDATA-600399b1-ae861d06-cb7b-000c29ff2470
vfat        4.0G 169.4M      3.8G   4% /vmfs/volumes/BOOTBANK1
vfat        4.0G  64.0K      4.0G   0% /vmfs/volumes/BOOTBANK2

ESXi 6.7インストール後、ESXi 7.0(ESXi 8.0)にアップグレード

1. ESXi 6.7を通常インストール

ESXi 6.7を通常インストールする。特別な手順は不要となる。この時点では、パーティションは以下の通りとなり、40GBのうち32.5GBがVMFS-6のデータストアとして作成されている。

[root@localhost:~] df -h
Filesystem   Size   Used Available Use% Mounted on
VMFS-6      32.5G   1.4G     31.1G   4% /vmfs/volumes/datastore1
vfat       249.7M 147.6M    102.1M  59% /vmfs/volumes/f471577b-dca3afaf-29c3-0d637cde3004
vfat       285.8M 195.8M     90.0M  68% /vmfs/volumes/60022d77-6ee455cf-0106-000c29369e16
vfat       249.7M   4.0K    249.7M   0% /vmfs/volumes/eb87dc73-7f38d4c9-e1f8-0b2e37658546
vfat         4.0G   4.2M      4.0G   0% /vmfs/volumes/60022d81-569c280c-e4f2-000c29369e16

2. ESXi 6.7をシャットダウン

ESXi 7.0にアップグレードするため、ESXi 6.7を一度シャットダウンする。

3. ESXi 7.0(ESXi 8.0)のインストーラにてブート

ESXi 7.0(ESXi 8.0)のインストールISOイメージを使って、ESXi 7.0(ESXi 8.0)のインストーラをブートする。

Nested ESXiの環境は、仮想ディスクのブート順位が高いため、仮想マシンの「設定の編集」→「仮想マシン オプション」→「起動オプション」→「強制的にBIOSセットアップ」にチェックを入れたのち、仮想マシンを起動させる。

起動にBIOSのBoot Managerが表示されるので、「EFI VMware Virtual SATA CDROM Drive (0.0)」を選択し、ESXi 7.0のISOイメージから起動を実施する。

4. ESXi 7.0(ESXi 8.0)にアップグレード

ESXiをアップグレードするが、手順としては通常インストールとほぼ同一となる。唯一の変更点は、インストールディスク選択時に「ESXi and VMFS found」の画面が表示されるので、必ず「Upgrade ESXi, preserve VMFS datastore」を選択すること。

5. インストール後のパーティションの確認

インストール後のディスクパーティションを確認してみると、OS領域だけでなく、VMFS-6のデータストアも作成されていることがわかる。ESXi 6.7のデータストアは維持されるため、ESXi 7.0(ESXi 8.0)となっても、データストアの容量は32.5GBの容量から変化しない。

[root@localhost:~] df -h
Filesystem   Size   Used Available Use% Mounted on
VMFS-6      32.5G   1.4G     31.1G   4% /vmfs/volumes/datastore1
VFFS         6.2G   2.7G      3.5G  44% /vmfs/volumes/OSDATA-600248ad-7bcf04bf-134c-000c29369e16
vfat       499.7M 166.1M    333.6M  33% /vmfs/volumes/BOOTBANK2
vfat       499.7M   8.0K    499.7M   0% /vmfs/volumes/BOOTBANK1

参考

更新履歴

  • 2021/3/30 新規作成
  • 2022/10/15 ESXi 8.0の記述を追記
2022年10月8日土曜日

ESXi 6.7 Update 3をESXi 7.0 Update 3にバージョンアップする手順

ESXi 6.7は2022/10/15にGENERAL SUPPORTが終了となる(TECHNICAL GUIDANCEはその1年後)。自宅では、2018年から長らくESXi 6.7を使って稼働させてきた。

しかし、上記サポート終了を受け、とうとう自宅サーバをESXi 6.7 Update 3からESXi 7.0 Update 3にバージョンアップした。バージョンアップ自体は問題なく実施できたが、他にも同様にバージョンアップをする場合に参考になるかもしれないので、本記事では、私が実施したESXi 6.7 Update 3からESXi 7.0 Update 3にバージョンアップする手順を記載する。

環境

今回の環境は以下の通り。

  • バージョンアップ前:ESXi 6.7 Update 3
  • バージョンアップ後:ESXi 7.0 Update 3

ESXi 6.7に追加で個別にVIBをインストールしていたりすると、バージョンアップ時にエラーとなり失敗するケースがある。例えば、ESXi 7.0から利用できないRealtek製NICのドライバなどがインストールされているとエラーとなる可能性がある。

そこで今回は直接バージョンアップするのではなく、一度ESXi 6.7をクリーンインストールしたのち、ESXi 7.0にバージョンアップする手順としている。またこの方法を用いると、ESXi 7.0のインストール領域が最小限に抑えることもできる。詳細は以下記事を参照いただきたい。

ESXi 6.7 Update 3からESXi 7.0 Update 3へのバージョンアップ手順

1. (Realtek製NICを使用している場合) Intel NICなどにNIC変更を実施

ESXi 6.7ではRealtek製NICであっても、有志が作成したドライバを導入することでESXiのNICとして認識させることができた。しかし、ESXi 7.0ではRealtek製NICをサポートしておらず使用することができない

そのため、既存環境でRealtek製NICを使用している場合は、新たにPCIカードのIntel製NICに変更するか、USB NICなどに変更する必要がある。

USB NICを使用する場合は、使用できるチップセットや事前に必要となるVIBファイルなどがあるため、詳細は以下記事を参照いただきたい。

ちなみに、私の自宅サーバはIntel製NICが4ポートあり空きポートが存在いたことから、仮想スイッチの物理NICを変更することで対処した。

2. PowerCLIで設定値保存

ESXiクリーンインストール前に、設定値を保存しておく。今回は突貫ではあるが以下のようなPowerCLIの簡単なコマンド一覧を使って設定値の保存を行った。

# Hardware
Get-VMHostHardware | fl *
Get-VMHostPciDevice | ft -AutoSize -Wrap

# Host
Get-VMHost | fl *
Get-VMHostAccount | ft -AutoSize -Wrap
Get-VMHostAdvancedConfiguration | ft -AutoSize -Wrap
Get-VMHostService | ft -AutoSize -Wrap

(Get-VMHost | Get-EsxCli).system.snmp.get()

# Network
Get-VMHostNetworkAdapter | ft -AutoSize -Wrap
Get-VMHostNetwork | fl *
Get-VMHostNetworkStack | fl *
Get-VMHostFirewallException | ft -AutoSize -Wrap
Get-VMHostRoute | ft -AutoSize -Wrap

Get-VirtualNetwork | ft -AutoSize -Wrap
Get-VirtualPortGroup | ft -AutoSize -Wrap
Get-VirtualSwitch | ft -AutoSize -Wrap
Get-VirtualSwitch | Get-SecurityPolicy | ft -AutoSize -Wrap

# Disk & Datastore
Get-VMHostDisk | fl *
Get-VMHostDiskPartition | ft -AutoSize -Wrap
Get-VMHostHba | fl *
Get-VMHostStorage | fl *

Get-Datastore | ft -AutoSize -Wrap

# VM
Get-VM

3. ESXi 6.7をクリーンインストール

ESXi 6.7 Update 3をクリーンインストールする。手順自体はインストールISOイメージをマウントしてインストールするだけなので詳細は割愛するが、途中確認される「ESXi and VMFS Found」の画面では、必ず「Install ESXi, preserve VMFS datastore」を選択すること。ここを間違えると、データストアにある仮想マシンが消失するので注意する。

インストールできたら、最低限ネットワークとVMkernelポートの設定を行い、データストアが問題なく認識していることを確認しておこう。

4. ESXi 7.0にバージョンアップ

続いて、ESXi 7.0 Update 3にバージョンアップする。手順自体はインストールISOイメージをマウントしてインストールするだけなので詳細は割愛するが、途中確認される「ESXi and VMFS Found」の画面では、必ず「Upgrade ESXi, preserve VMFS datastore」を選択すること。ここを間違えると、データストアにある仮想マシンが消失するので注意する。

インストールできたら、最低限ネットワークとVMkernelポートの設定を行い、データストアが問題なく認識していることを確認しておこう。

5. 各種設定を実施

後は、もともとのESXiに設定していた各種設定を反映させよう。私の環境の場合は以下設定の反映が必要だった。

  • VMkernel設定
  • 仮想スイッチ設定
  • ポートグループ設定
  • NTP設定
  • ユーザ及びロール設定
  • SNMP Trap設定
  • ESXi起動時の仮想マシンの自動起動設定

以上で、ESXi 6.7 Update 3からESXi 7.0 Update 3にバージョンアップする手順は完了となる。本手順でバージョンアップした自宅サーバは、バージョンアップ後、特に問題なく稼働している。


Seleniumを導入してChromeとEdgeを自動操作する

Webアプリケーションのテスト自動化のツールとして、Seleniumがある。これはWebブラウザの操作限定ではあるが、RPAツールのようにブラウザの操作自動化が可能となる。

今回は、Seleniumの導入手順と簡単な自動操作を行うPythonスクリプトを作成したので紹介する。

環境

今回はWindows環境に導入済みのPythonを用いてSeleniumを実行させる。ブラウザはChromeとEdgeの2種類を利用する。

  • OS : Windows Server 2022
  • Python : 3.10.6
  • Chrome : バージョン: 105.0.5195.102(Official Build) (64 ビット)
  • Edge : バージョン 105.0.1343.33 (公式ビルド) (64 ビット)

Selenium導入手順

1. pipにてSeleniumを導入

作業はすべてPowerShellのコンソールにて実施する。Seleniumはpipにてすぐにインストールできる。

PS> pip install selenium
Collecting selenium
  Downloading selenium-4.4.0-py3-none-any.whl (985 kB)
     ---------------------------------------- 985.8/985.8 kB 20.8 MB/s eta 0:00:00
Collecting urllib3[secure,socks]~=1.26
  Downloading urllib3-1.26.11-py2.py3-none-any.whl (139 kB)
     ---------------------------------------- 139.9/139.9 kB ? eta 0:00:00

~(中略)~

Collecting h11<1,>=0.9.0
  Downloading h11-0.13.0-py3-none-any.whl (58 kB)
     ---------------------------------------- 58.2/58.2 kB ? eta 0:00:00
Installing collected packages: sortedcontainers, urllib3, sniffio, PySocks, pycparser, idna, h11, certifi, attrs, async-generator, wsproto, outcome, cffi, trio, cryptography, trio-websocket, pyOpenSSL, selenium
Successfully installed PySocks-1.7.1 async-generator-1.10 attrs-22.1.0 certifi-2022.6.15 cffi-1.15.1 cryptography-37.0.4 h11-0.13.0 idna-3.3 outcome-1.2.0 pyOpenSSL-22.0.0 pycparser-2.21 selenium-4.4.0 sniffio-1.2.0 sortedcontainers-2.4.0 trio-0.21.0 trio-websocket-0.9.2 urllib3-1.26.11 wsproto-1.1.0

2. ブラウザ操作用のWebDriverのプログラムをダウンロード

Seleniumはブラウザに対してWebDriverと呼ばれるプログラムを経由して操作を行う。WebDriverはブラウザごとに異なるプログラムが提供されている。今回はChromeとEdgeの2パターンで試すことにする。

ChromeとEdgeのWebDriverは以下URLからダウンロードできる。操作対象のブラウザとWebDriverのバージョンは一致したものをダウンロードしよう。あまりにもバージョンの差異があると実行に失敗するようだ。

操作対象ブラウザ ダウンロードURL ダウンロードファイル名 WebDriverファイル名
Chrome ChromeDriver - WebDriver for Chrome - Downloads chromedriver_win32.zip chromedriver.exe
Edge Microsoft Edge WebDriver - Microsoft Edge Developer edgedriver_win64.zip msedgedriver.exe

ブラウザのバージョン確認はPowerShellなどでも確認可能だが、GUIから確認した方が手っ取り早い。Chromeであれば「ヘルプ」の「Google Chromeについて」から確認できる。

図021

また、ダウンロードしたWebDriverのバージョンはファイルのプロパティから確認することができるが、都度確認するのも面倒なのでバージョンがすぐに判別できるようファイル名を変更しておくのもよいだろう(例:chromedriver.exechromedriver_105.exe)。

3. Selenium実行用Pythonスクリプトを作成

今回は例として、Chromeにて以下操作を実行するPythonスクリプトを作成した。

  1. ブラウザをシークレットモードで起動しGoogleを表示
  2. ウィンドウを最大化
  3. 「Selenium」を検索
  4. 検索結果の1つ目のURLに遷移
  5. 画面キャプチャ取得
  6. ブラウザを閉じる

以下に実際のスクリプト(selenium_google.py)を記載する。いくつかポイントを記載する。

  • 今回はChromeで実行できるよう作成したが、Edgeで実行する場合は、「Chromeの場合」の個所をコメントアウトし、「Edgeの場合」の個所をアンコメントすれば動作する。
  • ブラウザで操作する要素を検索するため、XPath (XML Path Language)と呼ばれる表記方法を用いてる。スクリプトに記述するXPathの調査方法については後述する。
  • 画面遷移が完了するよりも前にスクリプトの検索が動作する場合、対象の要素が見つからずに処理が失敗することがある。そのため、要素が見つからない場合でも各処理ごとに待機するようdriver.implicitly_wait(10)を設定する。これによって要素が見つかるまで、最大10秒間待機することができる。
import time
from selenium import webdriver

# Chromeの場合
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

# Edgeの場合
# from selenium.webdriver.edge.service import Service
# from selenium.webdriver.edge.options import Options

# 接続先URL
url = "https://www.google.co.jp/"

# 1. ブラウザをシークレットモードで起動しGoogleを表示
options = Options()

# Chromeの場合
options.add_argument('--incognito')
driver = webdriver.Edge(service=Service('chromedriver_105.exe'), options=options) # Chrome Driver
# Edgeの場合
# options.add_argument('--inprivate')
# driver = webdriver.Edge(service=Service('msedgedriver.exe'), options=options) # Edge Driver

driver.implicitly_wait(10) # 待機時間の設定(秒)

driver.get(url) # URLを開く

# 2. ウィンドウを最大化
driver.maximize_window()

# 3. 「Selenium」を検索
driver.find_element("xpath", '/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/input').send_keys("Selenium") # 検索ワードを入力
driver.find_element("xpath", '/html/body/div[1]/div[3]/form/div[1]/div[1]/div[3]/center/input[1]').click() # [Google検索]ボタンをクリック

# 4. 検索結果の1つ目のURLに遷移
driver.find_element("xpath", '//*[@id="rso"]/div[1]/div/div/div[1]/div/a/h3').click() # 検索結果の1つ目をクリック

# 5. 画面キャプチャ取得
driver.save_screenshot("c:\\work\\selenium_google.png")

# 6. ブラウザを閉じる
driver.quit()

4. Selenuimを実行

先ほど作成したPythonスクリプトとWebDriverの実行ファイルを同一フォルダに配置し以下コマンドにて実行すれば、自動でブラウザが起動し操作が実行される。

PS> python.exe .\selenium_google.py

なお、Seleniumで実行した場合、「Chromeは自動テスト ソフトウェアによって制御されています。」と画面上部に表示される。

以上で、Seleniumの導入手順とスクリプトの説明は完了となる。

XPathの調査方法

Seleniumで操作対象の要素を検索する際に利用するXPathの調査方法を以下に記載する。

1. ブラウザの開発者ツールを起動

ChromeやEdgeにて操作対象のURLを開き、「F12」キーを押して開発者ツールを起動する。

2. 操作対象の要素を特定

Chromeの場合は、操作対象の要素(今回はGoogleの検索キーワードを入力欄)を右クリックし、「検証」をクリックする。Edgeの場合は、「開発者ツールで調査する」をクリックする。すると、開発者ツールにて対象の要素がハイライトされる。

3. 開発者ツールでXPathをコピー

開発者ツールで特定した要素を右クリックし「Copy」→「Copy XPath」を選択すると、クリップボードにXPathの内容がコピーされる。

以上で、XPathの調査方法の説明は完了となる。

更新履歴

  • 2022/10/8 待機処理をdriver.implicitly_wait(10)の処理に変更

2022年10月1日土曜日

PostgreSQLのDBを手動フェイルオーバーする手順 (ストリーミングレプリケーション構成)

以下記事にてPostgreSQLをストリーミングレプリケーションで構成する手順を記載した。

ストリーミングレプリケーション構成では、更新・参照が可能なマスターと、参照のみ可能なスタンバイの構成となるが、マスターのDBが障害により停止した場合は、スタンバイのDBを更新可能とする必要がある。このようにスタンバイのDBをマスターDBとして昇格する手順を「フェイルオーバー」と呼ぶ。

PostgreSQLは自動でフェイルオーバーする機能は提供されておらず、PacemakerやPgpool-IIなど、別製品を組み合わせて実現するパターンが多いと想定されるが、今回は手動でPostgreSQLをフェイルオーバーする手順を記載する。

環境

OSはRHEL 8を利用し、ディストリビューションでパッケージ提供されているPostgreSQLのバージョンをそのまま利用する。

  • OS : Red Hat Enterprise Linux 8.2
  • PostgreSQL : 10.6

すでに以下記事の手順にてPostgreSQLのストリーミングレプリケーションは構成されていることを前提とする。

今回は1号機マスター、2号機スタンバイとして構成したものを、1号機スタンバイ、2号機マスターとして入れ替える。作業対象がわかりやすくなるよう、プロンプトを以下のように記載する。

プロンプト 説明
[#1(Master/Standby)] 1号機で作業を実施。()はMaster/Standbyのいずれかを記載。
[#2(Master/Standby)] 2号機で作業を実施。()はMaster/Standbyのいずれかを記載。

手動フェイルオーバー手順

1. マスターのDBを停止

マスターのDBをpg_ctlコマンドで停止する。systemctlコマンドで停止も可能だが、その場合パスワード認証を求められるため、作業簡略化のためpg_ctlコマンドを用いる。

[#1(Master)]$ pg_ctl stop -D ${PGDATA}

2. スタンバイのDBを更新可能となるよう昇格

スタンバイのDBを更新可能にするため、昇格 (Promote) する。これをすると、スタンバイの設定が記載されていたrecovery.confrecovery.doneというファイル名に変更され、それによってスタンバイとしてのDB設定が無効化される。

[#2(Standby)]$ pg_ctl promote -D ${PGDATA}

3. ストリーミングレプリケーションの同期を停止

スタンバイDBを昇格しマスターDBとなったとしても、スタンバイDBが停止しているためWAL転送ができず、更新完了の応答を返すことができない。

以下はこの状態であえてDBに更新を行った際のエラーメッセージとなる。ローカルではコミットされているが、スタンバイにレプリケーションされていない旨のメッセージが表示されている。

[#2(Master)]$ psql -d testdb -c "insert into mytable (id, name) values (4, 'Shiro');"
^CCancel request sent
WARNING:  canceling wait for synchronous replication due to user request
DETAIL:  The transaction has already committed locally, but might not have been replicated to the standby.
INSERT 0 1

これはDBとしては正常な動作であり、postgresql.confsynchronous_commitの設定値がonであったりremote_writeであったりすると発生する。本事象回避のため、一時的に同期レプリケーションを停止させるため、synchronous_standby_namesの値を空白に設定する。

[#2(Master)]$ sed -ie "s/^synchronous_standby_names = '\*'/synchronous_standby_names = ''/g" ${PGDATA}/postgresql.conf
[#2(Master)]$ pg_ctl reload -D ${PGDATA}

これによって、以下の通りDBの更新が可能となる。

[#2(Master)]$ psql -d testdb -c "delete from mytable where id=4;"
DELETE 1
[#2(Master)]$ psql -d testdb -c "insert into mytable (id, name) values (4, 'Shiro');"
INSERT 0 1

4. 旧マスターのDBをスタンバイとして設定

旧マスターのDBをスタンバイとして動作させるため、recovery.donerecovery.confにリネームする。

[#1(Standby)]$ mv ${PGDATA}/recovery.done ${PGDATA}/recovery.conf

もしrecovery.doneが存在しない場合(過去一度もrecovery.confを設定したことがない場合)は、以下のように新規作成する。IPアドレスは新マスターのIPアドレスを指定すること。

[#1(Standby)]$ vi $PGDATA/recovery.conf
standby_mode = 'on'
primary_conninfo = 'application_name=t1116psgl user=dbrepl password=''P@ssw0rd'' host=192.168.11.117 port=5432'
recovery_target_timeline = 'latest'
restore_command = 'scp -o StrictHostKeyChecking=no 192.168.11.117:$PGDATA/archivedir/%f "%p"'

recovery.confを作成後、DBをリロードし設定を反映させる。

[#1(Standby)]$ pg_ctl start -D ${PGDATA}

5. ストリーミングレプリケーションの同期を再開

前の手順で変更したsynchronous_standby_namesをもとの設定値である*に戻す。

[#2(Master)]$ sed -ie "s/^synchronous_standby_names = ''/synchronous_standby_names = '\*'/g" ${PGDATA}/postgresql.conf
[#2(Master)]$ pg_ctl reload -D ${PGDATA}

6. ストリーミングレプリケーションの状態確認

最後にマスターとなったDBにて、レプリケーションの状態を確認しよう。sync_statesyncとなっていれば問題なくマスターとスタンバイでストリーミングレプリケーションが構成できている。

[#2(Master)]$ psql -x -c "SELECT * FROM pg_stat_replication;"
-[ RECORD 1 ]----+------------------------------
pid              | 6616
usesysid         | 16384
usename          | dbrepl
application_name | t1116psgl
client_addr      | 192.168.11.116
client_hostname  |
client_port      | 55756
backend_start    | 2022-06-10 10:58:02.832958+09
backend_xmin     |
state            | streaming
sent_lsn         | 0/701CA08
write_lsn        | 0/701CA08
flush_lsn        | 0/701CA08
replay_lsn       | 0/701CA08
write_lag        |
flush_lag        |
replay_lag       |
sync_priority    | 1
sync_state       | sync

以上で、手動でPostgreSQLをフェイルオーバーする手順は完了となる。

フェイルオーバー手順をスクリプト化する

フェイルオーバーの手順を毎回手作業で実施することは、手間を要するだけでなく、順序を誤ると最悪スタンバイのDB再構築 (ベースバックアップのコピーから実施) が必要となる。

そのため、本作業を自動化すべく、以下の通りスクリプト化してみた。以下にスクリプトの内容を記載する。本スクリプトはスタンバイのDBで実行する。また、互いにSSHの鍵交換を行いパスワードなしでSSHできることが前提となる。

#!/bin/bash

# PostgreSQLのストリーミングレプリケーションをフェイルオーバーするスクリプト

set -eu

# 本スクリプトはスタンバイのDBで実行すること
# スタンバイであるかをチェック
if [ $(psql -xt -c "SELECT * FROM pg_stat_replication;" | grep -c sync_state) -ne 0 ]; then
  echo "[ERROR] This DB is a master. Please run it on standby DB."
  exit 1
fi

# DBのIPアドレスを取得
myhost=$(nmcli c s ens192 | grep ipv4.addresses | sed -e 's#.* \(.*\)/.*#\1#g')
peer=$(grep -e 'primary_conninfo' ${PGDATA}/recovery.conf | sed -e 's/.*host=\(.*\) .*/\1/g')

# マスターの停止
ssh -o StrictHostKeyChecking=no $peer "pg_ctl stop -D ${PGDATA}"

# スタンバイからマスターへ昇格及びレプリケーション停止
pg_ctl promote -D ${PGDATA}
sed -ie "s/^synchronous_standby_names = '\*'/synchronous_standby_names = ''/g" ${PGDATA}/postgresql.conf
pg_ctl reload -D ${PGDATA}

# 旧マスターをスタンバイへ変更
ssh -o StrictHostKeyChecking=no $peer "mv ${PGDATA}/recovery.done ${PGDATA}/recovery.conf"
ssh -o StrictHostKeyChecking=no $peer "pg_ctl start -D ${PGDATA} -l /dev/null"

# レプリケーション再開
sed -ie "s/^synchronous_standby_names = ''/synchronous_standby_names = '\*'/g" ${PGDATA}/postgresql.conf
pg_ctl reload -D $PGDATA

exit 0

スクリプトはfailover.shという名前でpostgresユーザーのホームディレクトリなどに配置すればよい。実行権限も付与しておこう。

[Master/Standby]$ cd ~
[Master/Standby]$ vi failover.sh
[Master/Standby]$ chmod +x failover.sh

実際に本スクリプトを用いてフェイルオーバーを実施してみた結果が以下となる。

[#2(Standby)]$ ./failover.sh
サーバ停止処理の完了を待っています....完了
サーバは停止しました
サーバの昇格を待っています....完了
サーバは昇格しました
サーバにシグナルを送信しました
サーバの起動完了を待っています.....完了
サーバ起動完了
サーバにシグナルを送信しました

[#2(Master)]$ psql -x -c "SELECT * FROM pg_stat_replication;"
-[ RECORD 1 ]----+------------------------------
pid              | 11421
usesysid         | 16384
usename          | dbrepl
application_name | t1116psgl
client_addr      | 192.168.11.116
client_hostname  |
client_port      | 56022
backend_start    | 2022-06-11 15:44:35.698242+09
backend_xmin     |
state            | streaming
sent_lsn         | 0/150001A8
write_lsn        | 0/150001A8
flush_lsn        | 0/150001A8
replay_lsn       | 0/150001A8
write_lag        | 00:00:00.02531
flush_lag        | 00:00:00.02543
replay_lag       | 00:00:00.025536
sync_priority    | 1
sync_state       | sync

人気の投稿