2024年3月10日日曜日

PostfixにSPF検証機能を実装する

メールセキュリティの機能として知られるSPF検証は、迷惑メールなどの対策として、ほぼすべてのメールシステムで実装されている。

本記事では、PostfixにSPF検証機能を実装する手順を記載する。

環境

  • OS : AlmaLinux release 9.3
  • Postfix : 3.5.9
  • pypolicyd-spf : 2.9.3-4

導入手順

1. pypolicyd-spfをインストール

Postfixは単体ではSPF検証を行うことはできないので、pypolicyd-spfをdnfでインストールする。

# dnf install pypolicyd-spf -y

pypolicyd-spfの設定ファイルはシンプルな内容となっている。SPFレコードのチェックは、HELO時に記載されたドメインとMAIL FROMに記載されたドメインの2か所に対して実施し、受信拒否をすることができる。

今回はどちらもチェックさせるよう、そのままの設定とするが、受信拒否をさせたくない場合は、FailFalseに変更すればよい。また、このままでは内部から外部へメール送信時もSPFチェックがされてしまうので、ローカルのネットワークアドレスをskip_addressesに追加しておこう。

/etc/python-policyd-spf/policyd-spf.conf

#  For a fully commented sample config file see policyd-spf.conf.commented

debugLevel = 1
TestOnly = 1

HELO_reject = Fail
Mail_From_reject = Fail

PermError_reject = False
TempError_Defer = False

skip_addresses = 127.0.0.0/8,::ffff:127.0.0.0/104,::1,192.168.0.0/16

2. Postfixの設定

メール受信時にpolicyd-spfに渡すための設定を実施する。

/etc/postfix/master.cf

policyd-spf unix -      n       n       -       0       spawn
  user=nobody argv=/usr/libexec/postfix/policyd-spf

/etc/postfix/main.cf

smtpd_recipient_restrictions = check_policy_service unix:private/policyd-spf
policyd-spf_time_limit = 3600

設定後、Postfixのサービスを再起動し、設定を反映させる。

# systemctl restart postfix
# systemctl status postfix

3. 動作確認(SPF検証成功例)

実際にメールを受信した際に、SPF検証に成功している際のログを以下に記載する。

Mar 10 06:40:54 hoge policyd-spf[57430]: 
prepend Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=123.45.67.89; 
helo=mail-xxxx.google.com; envelope-from=hoge@gmail.com; receiver=<UNKNOWN>

通過したメールのヘッダーにもSPF検証がPassしている旨追記される。

4. 動作確認(SPF検証NGの例)

SPF検証がNGとなる場合は、HELOとMAIL FROMの場合で若干メッセージが異なるため、2パターンをそれぞれ記載する。

HELOのSPF検証NG

550 5.7.23 <tech-mmmm@hoge.tech-mmmm.com>: 
Recipient address rejected: Message rejected due to: SPF fail - not authorized. 
Please see http://www.openspf.net/Why?s=helo;id=example.com;ip=192.168.111.1;r=<UNKNOWN>

MAIL FROMのSPF検証NG

550 5.7.23 <tech-mmmm@hoge.tech-mmmm.com>: 
Recipient address rejected: Message rejected due to: SPF fail - not authorized. 
Please see http://www.openspf.net/Why?s=mfrom;id=from@hoge.tech-mmmm.com;ip=192.168.111.1;r=<UNKNOWN>

以上で、PostfixにSPF検証機能を実装する手順は完了となる。

更新履歴

  • 2024/3/10 新規作成
  • 2024/3/16 skip_addressesの記載を追記

2024年2月24日土曜日

Raspberry Pi 4上のUbuntuにKubernetesを構築する

本ブログでは、数回に分けてKubernetesの構築手順を記載してきた。

今回は、Raspberry Pi 4上のUbuntuに対してKubernetesを構築する手順を記載する。

環境

今回の手順を検証した環境は以下となる。

  • HW: Raspberry Pi 4 Model B(RAM 4GB)
  • OS: Ubuntu 22.04.4 LTS
  • コンテナランタイム: Docker : 24.0.1
  • CRI: cri-dockerd: 0.3.10-dev
  • Kubernetes: v1.28.5

手順

1. Dockerのインストール

Dockerのインストールはaptを使って実施するが、その際に利用する前提パッケージを先にインストールする。

sudo apt install apt-transport-https ca-certificates curl software-properties-common

Dockerのリポジトリを登録する。以前はapt-keyコマンドでも同様の登録ができてが、現在は非推奨となっていることから、curlでキー情報を入手し、登録する手順を実施する。

sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update

sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

Docker CEやその他必要となるパッケージをインストールする。

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Dockerの初期設定として、daemon.jsonを作成する。

$ sudo vi /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "insecure-registries": [
    "192.168.11.50"
  ]
}

Dockerのサービスを起動する。

sudo systemctl daemon-reload
sudo systemctl enable docker
sudo systemctl restart docker

2. Kubernetesインストール

Dockerと同様、Kubernetesもaptでインストールするため、リポジトリを登録する。

Kubernetes 1.28.xを使用する場合

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring_1.28.gpg
echo \
  'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | \
  sudo tee /etc/apt/sources.list.d/kubernetes_1.28.list
sudo apt update

Kubernetes 1.29.xを使用する場合

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo \
  'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt update

kubelet、kubeadm、kubectlをインストールする。今回は既存のKubernetesクラスターに追加するため、バージョンを1.28.5に固定している。さらにapt-mark holdをすることで、インストールしたパッケージが想定されないタイミングでバージョンアップされないようにしている。

sudo apt-get install kubelet=1.28.5-1.1 kubeadm=1.28.5-1.1 kubectl=1.28.5-1.1
sudo apt-mark hold kubelet kubeadm kubectl

kubeletのサービスを起動する。

sudo systemctl daemon-reload
sudo systemctl enable kubelet
sudo systemctl restart kubelet

3. Swap無効化

Kubernetesの前提条件として、Swapを無効化する必要がある。Raspberry PiのUbuntuの場合は、そもそもSwapが設定されておらず、totalの値が0になっているため、このまま作業を続行する。

$ free
               total        used        free      shared  buff/cache   available
Mem:         3880992      226264     2100932        3552     1553796     3472052
Swap:              0           0           0

4. cri-dockerdインストール

cri-dokerdはGo言語でコンパイルする必要があるため、まずはGoのインストールを行う。なお、Ubuntuの標準リポジトリのGoはバージョンが古いため、PPA (Personal Package Archives)のリポジトリ追加を行ったうえでインストールを行う。

sudo add-apt-repository ppa:longsleep/golang-backports
sudo apt update
sudo apt install golang-go

なお、今回のインストールされたGoのバージョンは、1.21.7となる。

$ go version
go version go1.21.7 linux/arm64

cri-dockerdのビルドと配置を行う。

go build -o bin/cri-dockerd
sudo install -o root -g root -m 0755 bin/cri-dockerd /usr/local/bin/cri-dockerd
sudo cp -a packaging/systemd/* /etc/systemd/system
sudo sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

cri-dockerdのサービスを起動する。

sudo systemctl daemon-reload
sudo systemctl enable cri-docker.service
sudo systemctl start cri-docker.socket

5. Kubernetes起動

最後にKubernetesを起動するが、初めてKubernetesクラスターを構成する場合はkubeadm initを、既存のクラスターに参加する場合はkubeadm joinを行う。

それぞれ、過去記事を参考に対応をいただきたい。

新規にKubernetesクラスターを構成する場合

既存のKubernetesクラスターに参加する場合

今回は既存のクラスターに参加した。結果、以下の通りUbuntu 22.04.4 LTSのノードがKubernetesに登録できた。

$ kubectl get node -o=wide
NAME        STATUS   ROLES           AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                    KERNEL-VERSION              CONTAINER-RUNTIME
t1015rasp   Ready    control-plane   26h    v1.28.5   192.168.3.5   <none>        Ubuntu 22.04.4 LTS          5.15.0-1046-raspi           docker://25.0.3
t3051kube   Ready    control-plane   276d   v1.28.5   192.168.3.1   <none>        AlmaLinux 8.6 (Sky Tiger)   4.18.0-372.9.1.el8.x86_64   docker://24.0.6
t3052kube   Ready    control-plane   276d   v1.28.5   192.168.3.2   <none>        AlmaLinux 8.6 (Sky Tiger)   4.18.0-372.9.1.el8.x86_64   docker://24.0.6

以上で、Raspberry Pi 4上のUbuntuに対してKubernetesを構築する手順は完了となる。

Ansibleでタスク単位でタイムアウトを設定する

Ansibleでタスクを実行する際に、30秒以上応答が返ってこない場合、タイムアウトでエラーとなり処理が中断されてしまう。その際に、以下のようなエラーが表示される。

2024-02-10 13:06:29,478 p=2302695 u=root n=ansible | 
persistent connection idle timeout triggered, timeout value is 30 secs.
See the timeout setting options in the Network Debug and Troubleshooting Guide.

Ansibleのタイムアウトはデフォルトで30秒と設定されているが、タスクによっては時間を要するものがあり、タイムアウトを長くしたい場合がある。

本記事では、Ansibleでタスク単位でタイムアウトを設定する方法を記載する。

環境

今回の設定を確認した環境は以下の通り。

  • OS : AlmaLinux release 8.5
  • Ansible : ansible [core 2.15.3]

タスク単位でタイムアウトを設定する方法

タスク単位でタイムアウトを設定する場合は、タスクにvars設定を追加し、ansible_command_timeoutを設定する。

以下例はVyOSのファイアウォール設定のタスクとなるが、ルール数が多い場合、30秒では設定は完了しないことがあるため、120秒に時間を延ばしている。

# ファイアウォールルール設定
- name: Set firewall rules
  vyos.vyos.vyos_firewall_rules:
    config:
      - afi: ipv4
        rule_sets:
          - name: OUTSIDE-IN-RULE
            default_action: drop
            rules: "{{ vyos_firewall_rule_outside }}"
          - name: INSIDE-IN-RULE
            default_action: drop
            rules: "{{ vyos_firewall_rule_inside }}"
    state: replaced
  vars:
    ansible_command_timeout: 120

【参考】デフォルトのタイムアウト時間を変更する方法

ansible.cfgで以下を記載することで、デフォルトのタイムアウト時間を変更することができる。

[persistent_connection]  
command_timeout = 30

以上で、Ansibleでタスク単位でタイムアウトを設定する方法は完了となる。

参考

ネットワークデバッグおよびトラブルシューティングガイド - Ansible Documentation

2024年2月19日月曜日

Kubernetes構築手順① (cri-dockerdを使ってコントロールプレーンを構築)

今までminikubeを使ってKubernetes環境を手軽に構築をしてきたが、とうとう本家Kubernetesの構築を行うことにした。

Kubernetes構築は数回に分けて説明する。

本記事では、Kubernetesの管理機能であるコントールプレーンの構築手順を記載する。

なお、Kubernetesを利用する際は、CRI (Container Runtime Interface)と呼ばれるコンテナを操作するためのインタフェースを指定する必要がある。CRIにはcontainerd、CRI-O、Dockerなど選択できるが、今回はDocker用のCRIである「cri-dockerd」を利用する。

環境

以下に今回構築する各種ソフトウェアのバージョンを記載する。

  • ホストOS : AlmaLinux 8.6
  • Docker : 23.0.1
  • cri-dockerd: 0.3.1-dev
  • Kubernetes: v1.26.2

なお、私の環境ではインターネット接続時にプロキシ接続が必要となるため、プロキシ経由で接続するための設定も併せて行っている。

今回の構成の概要図を以下に記載する。

Dockerインストール

1. プロキシ設定 (必要な場合のみ)

私の環境ではプロキシ経由でなければインターネット接続ができないため、以下の通り環境変数を設定する。プロキシの環境変数について小文字と大文字の両方を設定している理由は、本環境変数はソフトウェアによっては小文字と大文字の区別をすることがあるため、念のため両方を設定している。

また、no_proxyの設定は、コントロールプレーンとなるホストのIPアドレスも指定している(今回であれば、192.168.11.51)。

export http_proxy=http://192.168.33.23:8080
export https_proxy=http://192.168.33.23:8080
export no_proxy=localhost,127.0.0.1,192.168.11.51,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
export HTTP_PROXY=http://192.168.33.23:8080
export HTTPS_PROXY=http://192.168.33.23:8080
export NO_PROXY=localhost,127.0.0.1,192.168.11.51,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16

2. Dockerインストール前の競合パッケージの削除

Kubernetesが利用するコンテナエンジンはDockerを用いることにする。まずは、Dockerをインストールする。

ただし、GUI環境をインストールしたAlmaLinuxの場合、以下の通りpodmanとcontainers-commonのパッケージが競合する旨のエラーでインストールに失敗する。

# dnf install docker-ce docker-ce-cli containerd.io -y
メタデータの期限切れの最終確認: 0:05:01 時間前の 2022年11月26日 12時46分04秒 に 実施しました。
エラー:
 問題 1: インストール済パッケージの問題 podman-2:4.0.2-6.module_el8.6.0+2878+e681bc44.x86_64
  - パッケージ podman-2:4.0.2-6.module_el8.6.0+2878+e681bc44.x86_64 には runc >= 1.0.0-57 が必要ですが、どのプロバイダーからもインストールできません
  - パッケージ podman-3:4.2.0-4.module_el8.7.0+3344+484dae7b.x86_64 には runc >= 1.0.0-57 が必要ですが、どのプロバイダーからもインストールできません

~(中略)~

 問題 2: インストール済パッケージの問題 containers-common-2:1-27.module_el8.6.0+2878+e681bc44.x86_64
  - パッケージ containers-common-2:1-27.module_el8.6.0+2878+e681bc44.x86_64 には runc が必要ですが、どのプロバイダーからもインストールできません
  - パッケージ containers-common-2:1-43.module_el8.7.0+3344+484dae7b.x86_64 には runc が必要ですが、どのプロバイダーからもインストールできません

~(中略)~

このような場合は、一度podmanとcontainers-commonのパッケージを削除したうえで、Dockerをインストールしよう。

# dnf remove podman containers-common

3. Dockerをインストール

Dockerのインストールは以下の通りコマンドを実行すればよい。

# dnf install yum-utils -y
# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo

# dnf install docker-ce docker-ce-cli containerd.io -y

4. Docker起動時の設定

プロキシ環境の場合は、外部からコンテナのダウンロードができるよう、systemctlの起動設定に環境変数の設定をしておくこと。

# sed -ie '/\[Service\]/a Environment="http_proxy=http://192.168.33.23:8080" "https_proxy=http://192.168.33.23:8080" "no_proxy=localhost,127.0.0.1,192.168.11.51,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"' /usr/lib/systemd/system/docker.service

また、Dockerのサービス起動時の設定ファイルであるdaemon.jsonを以下の通り作成する。Kubernetesを利用する場合は、以下2点を設定する必要がある。

  • cgroupdrivercgroupfssystemdに変更
  • insecure-registriesに必要に応じてプライベートのDockerコンテナレジストリを設定
# cat << EOF > /etc/docker/daemon.json
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2",
  "storage-opts": [
    "overlay2.override_kernel_check=true"
  ],
  "insecure-registries": [
    "192.168.11.54"
  ]
}
EOF

5. Dockerサービスを起動

最後に、Dockerのサービスを起動しておく。

# systemctl daemon-reload
# systemctl start docker
# systemctl enable docker

Kubernetesインストール

1. Kubernetesパッケージのリポジトリ設定

Kubernetesのリポジトリとして、以下の通りファイルを作成する。

# cat <<EOF > /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.28/rpm/repodata/repomd.xml.key
#exclude=kubelet kubeadm kubectl
EOF

2. Kubernetesパッケージインストール

Kubernetesのインストールに必要なパッケージとして、kubeletkubeadmkubectlをインストールする。また、関連して必要なパッケージであるiproute-tcも併せてインストールする。

パッケージ 説明
kubelet Kubernetesの各ノードにインストールされるエージェントとなり、Podの起動などを制御するサービス。
kubeadm Kubernetesの構築を行うためのツール。コントロールプレーンの構築や、
kubectl Kubernetesの管理を行うためのツール。
iproute-tc Linux Traffic Control utilityのパッケージ。
# dnf install kubelet kubeadm kubectl --disableexcludes=kubernetes -y
# dnf install iproute-tc -y

3. kubelet起動

インストールしたkubeletのサービスを起動させておく。

# systemctl start kubelet
# systemctl enable kubelet

4. swapの無効化

Kubernetesでは前提としてswapの無効化が必要となるため、無効化のコマンドを実行し、/etc/fstabにてswap領域をマウントしないよう設定する。

# swapoff -a
# sed -ie 's|\(^/dev/.* swap .*\)|#\1|' /etc/fstab

cri-dockerdインストール

1. 前提パッケージインストール

cri-dockerdは、GitHubに公開されているソースを用いてビルドしインストールする。そのため、gitwgetのパッケージをインストールする。

# dnf install git wget -y

2. cri-dockrdをビルド及び配置

cri-dockerdはgo言語を用いているため、goコマンドをインストールする。

goコマンドをインストールするためのツールが用意されているので、ダウンロードしてインストールする。rootでコマンドを実行した場合は、/root/.go/bin/goにインストールされ、~/.bash_profileにもパスが追記される。

# mkdir /tmp/cri-dockerd
# cd /tmp/cri-dockerd
# wget https://storage.googleapis.com/golang/getgo/installer_linux
# chmod +x ./installer_linux
# ./installer_linux
# source ~/.bash_profile

次に、cri-dockerdのソースをgit cloneしダウンロードする。

# git clone https://github.com/Mirantis/cri-dockerd.git

ダウンロードしたソースをビルドし、systemdのディレクトリに配置する。

# cd cri-dockerd
# mkdir -p bin
# go build -o bin/cri-dockerd
# install -o root -g root -m 0755 bin/cri-dockerd /usr/local/bin/cri-dockerd
# cp -a packaging/systemd/* /etc/systemd/system
# sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' /etc/systemd/system/cri-docker.service

3. cri-dockrdを起動

cri-dockerdをサービスとして起動する。

# systemctl daemon-reload
# systemctl enable cri-docker.service
# systemctl enable --now cri-docker.socket

kubeadmコマンドを用いてKubernetesクラスター作成

1. kubeadmコマンド実行

kubeadmコマンドを用いて、Kubernetesクラスターの作成を行う。クラスターの作成はkubeadm initコマンドにて実施する。「Your Kubernetes control-plane has initialized successfully!」が表示されれば成功となる。

オプションの説明を以下に記載する。

オプション 説明
--pod-network-cidr=10.244.0.0/16 ネットワークアドオンとしてflannelを追加する際に必要となる設定となる。Pod用のクラスターネットワークのネットワークアドレスを指定する。
--cri-socket=unix:///var/run/cri-dockerd.sock CRIとしてcri-dockerdを使うことを明示的に指定するために指定する。指定しない場合、containerdとcri-dockerdの2つがインストールされていることにより、「Found multiple CRI endpoints on the host」のエラーにてクラスターの作成に失敗するので注意しよう。
# kubeadm init --pod-network-cidr=10.244.0.0/16 --cri-socket=unix:///var/run/cri-dockerd.sock
[init] Using Kubernetes version: v1.26.2
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[preflight] This might take a minute or two, depending on the speed of your internet connection
[preflight] You can also perform this action in beforehand using 'kubeadm config images pull'

~(中略)~

Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

Alternatively, if you are the root user, you can run:

  export KUBECONFIG=/etc/kubernetes/admin.conf

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.11.51:6443 --token bexxee.xx866gxx91sxxa69 \
        --discovery-token-ca-cert-hash sha256:06d8e3077c71a3f7af2xx8d6b389d43673xx9a8axxa5d003ed8dccb356a42f80

2. Kubernetesクラスター状態確認

Kubernetesの管理はkubectlコマンドを使って実施するが、そのままでは使用できないので、環境変数で設定ファイルのパスを指定しておく。

# export KUBECONFIG=/etc/kubernetes/admin.conf

それでは、kubectlコマンドを使って、PodとNodeの状態を確認してみよう。以下の通り、各種コントロールプレーン用のPodと1台のNodeが表示される。

# kubectl get pod -A
NAMESPACE     NAME                                READY   STATUS    RESTARTS   AGE
kube-system   coredns-787d4945fb-cf2sf            0/1     Pending   0          45s
kube-system   coredns-787d4945fb-wkp86            0/1     Pending   0          45s
kube-system   etcd-t1051kube                      1/1     Running   0          59s
kube-system   kube-apiserver-t1051kube            1/1     Running   0          60s
kube-system   kube-controller-manager-t1051kube   1/1     Running   0          60s
kube-system   kube-proxy-lscpc                    1/1     Running   0          46s
kube-system   kube-scheduler-t1051kube            1/1     Running   0          60s

# kubectl get node
NAME        STATUS     ROLES           AGE   VERSION
t1051kube   NotReady   control-plane   83s   v1.26.2

上記を見ると、corednsがPendingのステータスとなっており、NodeのステータスもNotReadyとなっている。これはPod用のネットワークアドオンがインストールされていないために発生しており、後続の手順にてflannelをインストールするとステータスがRunningになるため、現時点では気にしなくて問題ない

ネットワークアドオンインストール (flannelインストール)

1. flannelインストール

Kubernetesのネットワークアドオンはいくつか選択肢があるようだが、今回はflannnelを利用する。flannelはインストール用のマニフェストファイルが公開されており、Kubernetes環境に適用するだけでインストールできる。

# cd ~
# curl -LO https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# kubectl apply -f kube-flannel.yml
namespace/kube-flannel created
serviceaccount/flannel created
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
configmap/kube-flannel-cfg created
daemonset.apps/kube-flannel-ds created

2. インストール後のPodとNodeの状態確認

インストール後、1分ほど待ってから再度PodとNodeの状態を確認してみよう。PendingステータスだったcorednsがRunningになり、NodeのステータスもReadyになっているはずだ。

# kubectl get pod -A
NAMESPACE      NAME                                READY   STATUS    RESTARTS   AGE
kube-flannel   kube-flannel-ds-8s4cq               1/1     Running   0          41s
kube-system    coredns-787d4945fb-cf2sf            1/1     Running   0          2m43s
kube-system    coredns-787d4945fb-wkp86            1/1     Running   0          2m43s
kube-system    etcd-t1051kube                      1/1     Running   0          2m57s
kube-system    kube-apiserver-t1051kube            1/1     Running   0          2m58s
kube-system    kube-controller-manager-t1051kube   1/1     Running   0          2m58s
kube-system    kube-proxy-lscpc                    1/1     Running   0          2m44s
kube-system    kube-scheduler-t1051kube            1/1     Running   0          2m58s

# kubectl get node
NAME        STATUS   ROLES           AGE     VERSION
t1051kube   Ready    control-plane   3m17s   v1.26.2

以上で、Kubernetesの管理機能であるコントールプレーンの構築手順は完了となる。とはいえ、これだけでは実際にコンテナを起動させることもできないため、次回以降でワーカーノードを追加していく。

更新履歴

  • 2023/3/11 新規作成
  • 2024/2/19 リポジトリのURLを最新情報に更新

2024年2月18日日曜日

VyOSをソースからビルドして、ISOイメージを作成する

VyOSはRolliing releaseと呼ばれる非安定板であれば、ISOイメージを直接ダウンロードできるが、安定板は公式サイトから直接ダウンロードができない状況となっている。

安定板のVyOSを使いたい場合は、GitHubのソースからビルドすることで利用できる。

本記事では、VyOSをソースからビルドし、ISOイメージを作成する方法を記載する。なお、ビルドするバージョンはVyOS 1.3.5 (equuleus)とした。

環境

ビルドするためには、以下環境が必要となる。
  • dnfやaptを使用するため、インターネットに直接通信できること
    ※プロキシ環境で実施する場合は、OS、git、Dockerがインターネット通信できるように、適切にプロキシ設定を実施する必要があるが、手順が煩雑となるためお勧めできない
  • GitとDockerが動作する環境があること
今回は、AlmaLinux 9を最小インストールで構築し、そこにDockerをインストールして作業を実施してみた。

最短でVyOSをビルドするためのコマンド

最短でVyOSをビルドするコマンドを以下に記載する。上から順にコマンドを実行していけば、途中時間のかかる処理があるものの、2時間もあれば処理が完了するはずだ。私の環境ではCPU 1コアで2時間以内に完了した。ただし、yumやapt-getのダウンロード処理が多いため、インターネットの回線速度にも左右される。

git clone -b equuleus --single-branch https://github.com/vyos/vyos-build-bオプションでBranchをequuleusに指定し、VyOS 1.3.xを作成できるようにしている。もし、VyOS 1.2.xを利用したい場合はcruxを、VyOS 1.4.xを利用したい場合はSagittaを指定すればよい。

なお、ISOイメージビルド時に--versionオプションでバージョンを指定できるが、あくまでもイメージ作成後のVyOSで表示する際のバージョン表記を指定するものとなり、実際のバージョンはリポジトリにあるソースをもとにビルドされる。したがって、--versionで指定するバージョンは、任意の名前で指定しても問題ない。
# gitをインストール
dnf install git -y

# Dockerをインストール
dnf install yum-utils -y
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
dnf install docker-ce docker-ce-cli containerd.io -y
systemctl start docker

# ソースをダウンロード
git clone -b equuleus --single-branch https://github.com/vyos/vyos-build
# ISOイメージ作成用のコンテナをビルド
cd vyos-build/
docker build -t vyos/vyos-build:equuleus docker

# コンテナにてISOイメージをビルド(--versionでビルド後のVyOSで表示させるバージョンを指定)
docker run --rm -it --privileged -v $(pwd):/vyos -w /vyos vyos/vyos-build:equuleus bash
./configure --architecture amd64 --build-type release --version 1.3.5
sudo make iso
完了後、/vyos/build配下を確認すると「live-image-amd64.hybrid.iso」というISOイメージが作成されており、これがVyOSのISOイメージとなる(「vyos-x.y.z-amd64.iso」のISOも存在するが同じものとなる)。本ISOには、open-vm-toolsも組み込まれており、ESXiの仮想マシンに対して本ISOイメージをマウントしてインストールを実施すれば、いつものVyOSを構築することができる。
root@ad1ecd365c09:/vyos# ls -l build/*.iso
-rw-r--r--. 1 root root 373293056 Feb 10 02:29 build/live-image-amd64.hybrid.iso
-rw-r--r--. 1 root root 373293056 Feb 10 02:32 build/vyos-1.3.5-amd64.iso
なお、docker run実行時に-v $(pwd):/vyosでDockerホストのディレクトリをコンテナにマウントしているため、Dockerホスト側でも同じファイルを確認・取得することができる。
[root@localhost vyos-build]# ls -l build/*.iso
-rw-r--r--. 1 root root 373293056  2月 10 11:29 build/live-image-amd64.hybrid.iso
-rw-r--r--. 1 root root 373293056  2月 10 11:32 build/vyos-1.3.5-amd64.iso

参考

参考

  • 2020/5/7 新規作成
  • 2021/1/9 VyOS 1.2.6のバージョンに内容を修正
  • 2024/2/17 VyOS 1.3の実施結果をふまえ更新
  • 2024/2/18 --versionの説明を修正
2024年2月10日土曜日

OpenAI APIを使ってZabbixの障害イベントを生成AIに連携する

ChatGPTでおなじみのOpenAIは、OpenAI APIと呼ばれるAPIによる利用が可能となっている。APIを利用することで、curlコマンドなどを用いて手軽に生成AIの機能をシステムに組み込んで利用することができる。

本記事では、OpenAI APIを使ってZabbixの障害アラートを生成AIに連携する手順を記載する。

具体的には、OpenAI APIを用いて以下のプロンプトを連携し、Zabbixの障害アラートの原因の問い合わせを行う。問い合わせで得られた生成AIの応答をZabbixのメッセージとして表示できるようにする。

Please tell me the cause of the error message below.
[障害アラートの内容]

なお、本記事の作成にあたり、以下の記事も参考にさせていただいた。

環境

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

  • Zabbix 6.0.17

OpenAIのアカウントを作成していない場合は、事前に作成をしておこう。ChatGPTを利用している場合は、すでにアカウント作成済みとなるため、新たにアカウント作成は不要となる。

OpenAI APIのAPI keyを取得

1. OpenAI API keyの作成画面にログイン

以下URLにアクセスすると、OpenAI API keyの作成画面が表示される。

2. API keyを作成

「Create new secret key」ボタンを押し、API keyを作成する。

作成時にkeyの名前の入力を求められるが、未入力でも問題ない。

作成完了後、API keyが表示されるので、必ずコピーして控えること。ここでコピーしておかないと、以降確認することができないため、API keyを再作成する必要がある。

3. OpenAI APIの利用状況を確認

OpenAI APIはリクエストや応答に含まれるトークンの数によって課金される。トークンは英語の場合は1単語がおおよそ1トークンとなるようだが、日本語の場合は1文字が1トークン以上の場合があるとの情報があるので注意する。

トークンの使用状況は以下URLから確認することができる。

なお、アカウントを新規作成した際は、5.00 USD分の無料利用枠がある(3か月の期限付き)。テストする際にかなりAPIを使用してみたが、それでも0.03 USDしか消費しなかったので、5.00 USDあれば検証用途としては十分だろう。

OpenAI API連携用スクリプトの作成

1. curlによるOpenAI API実行方法

curlを使う場合は以下の通り実行すればOpenAI APIによる問い合わせができる。

# API keyを設定
export OPENAI_API_KEY='sk-********'

header='Content-Type: application/json'
apiurl='https://api.openai.com/v1/chat/completions'
prompt="ここに問い合わせしたい内容を記載。"
json='{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "'${prompt}'"}]}'

# API実行
curl ${apiurl} -sS -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "${header}" -d "${json}"

実行結果は以下の通り。.choices[0].message.contentがAIの回答となる。

# curl ${apiurl}  -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "${header}" -d "${json}"
{
  "id": "chatcmpl-xxxxxxxx",
  "object": "chat.completion",
  "created": 1695187433,
  "model": "gpt-3.5-turbo-0613",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The OpenAI mission is to ensure that artificial general intelligence (AGI) benefits all of humanity. AGI refers to highly autonomous systems that outperform humans in most economically valuable work. OpenAI aims to build safe and beneficial AGI directly, but it also considers its mission fulfilled if its work aids others in achieving this outcome. OpenAI commits to principles of broadly distributed benefits, long-term safety, technical leadership, and cooperative orientation to achieve the positive impact of AGI on society."
      },
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 14,
    "completion_tokens": 97,
    "total_tokens": 111
  }
}

2. スクリプト作成

curlによるOpenAI APIの実行方法を用いて、Zabbixの障害アラートを連携するスクリプトを以下の通り作成した。各環境に合わせて、OpenAIのAPI keyやZabbix API実行用のユーザとパスワードを設定すること。

障害アラートへのメッセージ追加はZabbix APIを使用する。Zabbix APIの使い方は、以下別記事を参考にしていただきたい。

また、OpenAI APIで同時に多数のリクエストを実行すると、応答がうまく得られずnullが返ってくる場合があったため、負荷分散を目的として、実行間隔制御の待機時間(最小10秒~最大120秒)を設定した。

send_chatgpt_api.sh

#!/bin/bash

# アラート内容を引数に代入
eventid=$1
alert=$(echo $2 | sed -e 's/"/\\"/g')

# 実行間隔制御 (10秒+ランダム時間)
sleep $(( 10 + ($RANDOM % 110) ))

# API keyを設定
export OPENAI_API_KEY='sk-********'

header='Content-Type: application/json'
apiurl='https://api.openai.com/v1/chat/completions'
prompt="Zabbixで検知した以下イベントメッセージの原因及び解消方法を簡潔に教えてください。\n\n${alert}"
json='{"model": "gpt-3.5-turbo", "messages": [{"role": "user", "content": "'${prompt}'"}]}'

# API実行
result=$(curl ${apiurl} -sS -H "Authorization: Bearer ${OPENAI_API_KEY}" -H "${header}" -d "${json}")

# 出力
result_message=$(echo $result | jq -r '.choices[0].message.content')

# Zabbix APIログイン
header='Content-Type:application/json-rpc'
apiurl='http://[ZabbixサーバのURL]/zabbix/api_jsonrpc.php'
json='{"jsonrpc": "2.0","method": "user.login","params": {"user": "Admin","password": "********"},"id": 1,"auth": null}'
zbxauth=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r ".result")

# Zabbix APIでイベントにメッセージ追加
json='{"jsonrpc": "2.0","method": "event.acknowledge","params": {"eventids": "'${eventid}'","action": 4,"message": "'$(echo ${result_message} | sed -e 's/"/\\"/g')'"},"auth": "'${zbxauth}'","id": 1}'
curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq

# Zabbix APIログオフ
json='{"jsonrpc": "2.0","method": "user.logout","params": [],"id": 4,"auth": "'$zbxauth'"}'
curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq

exit 0

3. スクリプトをZabbixサーバに配置

本スクリプトは、Zabbixサーバの/usr/local/binに配置し、実行権限を付与しておく。

# cp send_chatgpt_api.sh /usr/local/bin/
# chmod +x /usr/local/bin/send_chatgpt_api.sh
# ls -l /usr/local/bin/send_chatgpt_api.sh
-rwxr-xr-x 1 root root 1753  9月 23 07:41 /usr/local/bin/send_chatgpt_api.sh

実行方法は以下の通りとなる。

/usr/local/bin/send_chatgpt_api.sh '[イベントID]' '[アラート内容]'

Zabbixのトリガーアクションを設定

1. スクリプト設定

Zabbixのスクリプト設定を以下の通り行う。

設定項目 設定値
名前 Send ChatGPT
タイプ スクリプト
次で実行 Zabbixサーバー
コマンド /usr/local/bin/send_chatgpt_api.sh '{EVENT.ID}' '{ITEM.VALUE}'

2. トリガーアクション設定

Zabbixのトリガーアクション設定を以下の通り行う。今回は、「軽度の障害」以上の場合にスクリプトを実行するよう設定する。

設定項目 設定値
名前 Send ChatGPT
実行条件 「メンテナンス期間外」 AND 「トリガーの深刻度 以上 軽度の障害」

アクションの実行内容は以下の通り、先ほど作成したスクリプトを指定する。

設定項目 設定値
処理内容 Send ChatGPT
ターゲットリスト Zabbix server

動作確認

実際に障害アラートを発生させて、動作を確認してみよう。

例として、以下の障害アラートを発生させてみた。ポートチャネルPo1からgi3が除外されたアラートとなる。

SNMP Trap from t1250c250 : 2024/02/10 15:11:26. .1.3.6.1.4.1.9.6.1.101.0.161 
Normal General event UNKNOWN - %TRUNK-W-PORTREMOVED: Port gi3 removed from Po1 1

しばらくするとスクリプトが実行され、以下の通りOpenAI APIで取得した生成AIの回答がメッセージとして表示された。

このイベントメッセージの原因は、ポートgi3がPo1 1から削除されたことです。 
解決方法は、次のいずれかです。

  1. ネットワーク上でポートを再接続し、Po1 1にgi3を再度追加します。
  2. システムの設定を確認し、ポートが正しく構成されていることを確認します。
  3. 他のネットワークデバイスとの接続に問題がある場合は、接続を確認し、
     必要に応じて修正します。

これらの手順を実行することで、問題が解決する可能性があります。
ただし、具体的な状況に応じて解決方法が異なる場合もあるため、
詳細な情報があれば助かります。

以上で、OpenAI APIを使ってZabbixの障害アラートを生成AIに連携する手順は完了となる。

参考

更新履歴

  • 2023/9/23 新規作成
  • 2024/2/10 OpenAI APIに送るプロンプトを日本語に変更
2024年2月4日日曜日

Roundcubeでメール振り分け等のフィルター機能を有効にする

OSSのWebメールクライアントである「Roundcube」は、標準ではメール振り分けといったメールフィルターの機能を使用することができない。

Roundcubeはプラグインとして、機能を追加することができる。今回はRoundcubeにて「ManageSieve」のプラグインを有効化し、フィルター機能を利用できるようにする。

なお、ManageSieveを利用する際には、Dovecot PigeonholeをインストールしSieveスクリプトによるメール制御を行うことで、フィルター機能を実現することから、Postfix及びDovecot への設定追加が必要となる。

環境

Roundcubeのインストール手順は以下を参照いただきたい。

Roundcubeはあくまでもメールクライアントであるため、PostfixやDovecotによるメールサーバーの設定も必要となる。なお、今回は、バーチャルドメインのIMAPメールサーバーに対して設定を行っている。PostfixとDovecotの構築手順は以下を参照いただきたい。

構築環境としては以下となる。

  • OS : AlmaLinux 8.8
  • DB : MariaDB 10.3.35
  • Roundcube : 1.6.2
  • PHP : 7.4
  • Apache HTTP Server : 2.4.37
  • Postfix : 3.5.8
  • Dovecot : 2.3.16

Rondcubeフィルター有効化手順

1. Dovecot Pigeonholeをインストール

Dovecot Pigeonholeとは、メールの処理方法を定義する「Sieve」と呼ばれるスクリプト言語を用いてDovecot のメール配送処理を制御するためのソフトウェアとなる。

Dovecot Pigeonholeはdnfコマンドにてインストールできる。

dnf install dovecot-pigeonhole -y

2. Dovecot設定追加

Sieveを使えるようにするため、LDA (ローカル配送エージェント) 及びLMTP (Local Mail Transfer Protocol) の設定にsieveを追加する。

/etc/dovecot/conf.d/15-lda.conf

protocol lda {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve    # <- sieveを追加
}

/etc/dovecot/conf.d/20-lmtp.conf

protocol lmtp {
  # Space separated list of plugins to load (default is global mail_plugins).
  mail_plugins = $mail_plugins sieve    # <- sieveを追加
}

Dovecotにて、ManageSieveと呼ばれるSieveのアップロード等を管理するための機能を有効化する。設定ファイルの前半のコメントアウトされている行をアンコメントし有効化する。

/etc/dovecot/conf.d/20-managesieve.conf

# Uncomment to enable managesieve protocol:
protocols = $protocols sieve

# Service definitions

service managesieve-login {
  inet_listener sieve {
    port = 4190
  }

  inet_listener sieve_deprecated {
    port = 2000
  }

  # Number of connections to handle before starting a new process. Typically
  # the only useful values are 0 (unlimited) or 1. 1 is more secure, but 0
  # is faster. <doc/wiki/LoginProcess.txt>
  service_count = 1

  # Number of processes to always keep waiting for more connections.
  process_min_avail = 0

  # If you set service_count=0, you probably need to grow this.
  vsz_limit = 64M
}

Sieveのスクリプトファイル名は、デフォルトで.dovecot.sieveと隠しファイルになっているが、先頭に.が付いているとメールフォルダと誤認してしまう場合があるため、dovecot.sieveというファイル名に変更しておく。

/etc/dovecot/conf.d/90-sieve.conf

plugin {
  sieve = file:~/sieve;active=~/dovecot.sieve
}

以上が完了したら、Dovecotのサービスを再起動させる。

# systemctl restart dovecot

3. Postfix設定追加

Dovecotのローカル配送エージェントであるdovecot-ldaにメール配送をできるよう、master.cfに以下設定を追加する。

userはバーチャルメールボックスで使用するユーザー、グループであるvmailを指定する。

/etc/postfix/master.cf

dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/libexec/dovecot/dovecot-lda -f ${sender} -d ${recipient}

設定したdovecot-ldaへメールを配送するよう、main.cfに以下設定を追加する。

/etc/postfix/main.cf

virtual_transport = dovecot

以上が完了したら、Postfixのサービスを再起動させる。

systemctl restart postfix

4. Roundcubeで「ManageSieve」のプラグインを有効化

以下の設定を追加する。なお、設定は即時に反映されるため、サービス等の再起動は不要となる。

/etc/roundcubemail/config.inc.php

$config['plugins'] = array('managesieve');

5. 動作確認

実際にRoundcubeにログインすると、「設定」画面に「フィルター」が表示されるようになっていることがわかる。

ここで動作確認のため、以下の通りフィルターを作成する。

  • フィルター名 : テスト
  • 規則 : 「件名」、「含む」、「テストメール」
  • 操作 : 「次にメッセージを移動」、「test」フォルダ

実際に「これはテストメールです。」という件名のメールを送信すると、以下の通り、「test」フォルダにメールが配送されていることがわかる。

なお、Sieveのスクリプトファイルは、以下のように作成されていた。

# cat /var/spool/virtual/[mydomain]/[myuser]/Maildir/dovecot.sieve
require ["fileinto"];
# rule:[テスト]
if allof (header :contains "subject" "テストメール")
{
        fileinto "INBOX.test";
}

以上で、Roundcubeにて「ManageSieve」のプラグインを有効化する手順は完了となる。

2024年1月28日日曜日

SPF、DMARC、DKIMに対応するためのDNSとPostfix (OpenDKIM)設定手順

Gmailは2024年2月より、1日あたり5,000件以上のメールを送信する送信元においては、SPF(エスピーエフ)、DMARC(ディーマーク)、DKIM(ディーキム)の設定が必須となるよう、セキュリティポリシーが変更される。

私は自宅検証環境においてメール送受信できるPostfixのメールサーバを構築しているが、SPF以外のDMARCとDKIMの対応ができていなかった。

そこで、本記事では、DNSへレコード追加とPostfixへOpenDKIMを導入し、SPF、DMARC、DKIMに対応したメール送信環境を構築する手順を記載する。

環境

自宅検証環境の構成概要図は以下の通り。今回は、以下構成図の赤枠で示している内部のメールサーバのPostfixと、外部DNSとして利用している「お名前ドットコム」に対して設定変更を行う。

本記事で説明に用いるドメインはexample.tech-mmmm.comとするため、各自の所有するドメインに読み替えて参照いただきたい。

SPF

SPFへの対応はDNSへ登録するのみで完了できる。具体的には以下のようなSPFレコード (TXTレコード)をDNSに登録する。

example.tech-mmmm.com. 3600 IN TXT
  "v=spf1 +ip4:[送信元となるメールサーバのIPアドレス] -all"

DMARC

DMARCへの対応もSPFと同様、DNSへ登録するのみで完了できる。レコードは_dmarc.[ドメイン名]で登録するが、具体的には以下のようなDMARCレコード (TXTレコード)をDNSに登録する。

p=noneはポリシーの設定であり、今回はDMARCの結果において特に何もしない (None)という設定としている。

ruarufはDMARCの判定結果のレポートを送信する自ドメインのメールアドレスを設定すればよい。設定すると、Gmailからは1日1回おおよそAM 9:00 (UTCでいうと0:00)に「Report domain: example.tech-mmmm.com Submitter: google.com」というタイトルのメールがレポートとして送られてくるようになる。

_dmarc.example.tech-mmmm.com. 3600 IN TXT
  "v=DMARC1; p=none; rua=mailto:dmarc-report@example.tech-mmmm.com; ruf=mailto:dmarc-report@example.tech-mmmm.com;"

DKIM

DKIMに関してはメール送信時に署名を付与する仕組みが必要であり、Postfixの場合はMilter (Mail Filter)として動作するOpenDKIMを使用する。

以下に導入手順を記載する。

1. OpenDKIMインストール

EPELのリポジトリ追加後、以下をインストールする。opendkim-toolsに鍵情報を作成するコマンドが含まれているため、併せてインストールすること。

# dnf install epel-release -y
# dnf install opendkim opendkim-tools -y

2. DKIM署名用の鍵を作成

DKIM署名用の鍵は、opendkim-genkeyコマンドで行う。

-sオプションでセレクタと呼ばれる任意の識別するための文字列を指定できる。今回はmyselectorと設定した。-bオプションは鍵の長さを設定するオプションとなり、OpenDKIMのデフォルトは1024bitとなるが、セキュリティの観点から2048bitで設定する。

# mkdir /etc/opendkim/keys/example.tech-mmmm.com
# opendkim-genkey -D /etc/opendkim/keys/example.tech-mmmm.com/ -d example.tech-mmmm.com -s myselector -b 2048
# ls -l /etc/opendkim/keys/example.tech-mmmm.com/
合計 8
-rw-------. 1 root root 887  1月 27 18:01 myselector.private
-rw-------. 1 root root 328  1月 27 18:01 myselector.txt

鍵情報のファイルは、ユーザー・グループを変更しておく。

# chown -R opendkim:opendkim /etc/opendkim/keys/example.tech-mmmm.com/
# ls -l /etc/opendkim/keys/example.tech-mmmm.com/
合計 8
-rw-------. 1 opendkim opendkim 887  1月 27 18:01 myselector.private
-rw-------. 1 opendkim opendkim 328  1月 27 18:01 myselector.txt

3. 設定ファイル修正

OpenDKIMの設定ファイルであるopendkim.confを以下の通り修正する。

# vi /etc/opendkim.conf
Mode    sv						    # vからsvに変更
Socket  inet:8891@localhost				    # アンコメント (UNIXドメインソケットからinetソケットに変更)
#Socket local:/run/opendkim/opendkim.sock		    # コメントアウト
Selector        myselector				    # defaultから変更
KeyTable        /etc/opendkim/KeyTable			    # アンコメント
SigningTable    refile:/etc/opendkim/SigningTable	    # アンコメント
ExternalIgnoreList      refile:/etc/opendkim/TrustedHosts   # アンコメント
InternalHosts   refile:/etc/opendkim/TrustedHosts	    # アンコメント

セレクタとドメインのに対して使用する鍵情報を紐づけるKeyTableファイルを以下の通り記載する。

# vi /etc/opendkim/KeyTable
myselector._domainkey.example.tech-mmmm.com example.tech-mmmm.com:myselector:/etc/opendkim/keys/myselector.private

署名する際のメールのドメインと使用する鍵情報を紐づけるSigningTableファイルを以下の通り記載する。

# vi /etc/opendkim/SigningTable
*@example.tech-mmmm.com myselector._domainkey.example.tech-mmmm.com

OpenDKIMが信頼できるホストとして認識するホストやネットワークを指定する。今回は内部環境となるので、0.0.0.0/0で指定する。

# vi /etc/opendkim/TrustedHosts
0.0.0.0/0

4. OpenDKIMサービスを起動

設定完了後、OpenDKIMのサービスを起動させる。

# systemctl start opendkim
# systemctl enable opendkim

起動するとポート8891番でLISTENしているかどうか確認しておく。

# ss -nl | grep 8891
tcp   LISTEN 0      128                                127.0.0.1:8891             0.0.0.0:*

5. Postfixの設定修正

OpenDKIMをMilterとして指定するため、以下3行を追加する。

# vi /etc/postfix/main.cf
smtpd_milters = inet:127.0.0.1:8891
non_smtpd_milters = $smtpd_milters
milter_default_action = accept

設定完了後、Postfixを再起動しておく。

# systemctl restart postfix

6. DNS設定

DKIMはDNSの登録された公開鍵情報を用いて署名の検証を行うため、DNSへレコードの追加が必要となる。レコードは[セレクタ名]._domainkey.[ドメイン名]で登録する。レコードの内容は鍵を生成した際に作成される、/etc/opendkim/keys/example.tech-mmmm.com/myselector.txtのファイルに記載されているで、このまま登録を行えば問題ない。

# cat /etc/opendkim/keys/example.tech-mmmm.com/myselector.txt
myselector._domainkey   IN      TXT     ( "v=DKIM1; k=rsa; "
          "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyLeUjkqVc712NhH1/20UMGW4rxg4F4sQilrauxt5mWPEbu342y+Px+OD4oVZ1yeF9NAAAgExIJBwl854p1bn87rVgam9J/3mYICCD5GhbBy6MEFODgMZXcSvJ8Q2g7S1Y0mro95mI2p+8WtPfxjUTzERemcduNa3eZu7+i1I1GwIDAQAB" )  ; ----- DKIM key myselector for example.tech-mmmm.com

上記の通りDNSに登録を行う。

myselector._domainkey.example.tech-mmmm.com. 3600 IN TXT
  "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyLeUjkqVc712NhH1/20UMGW4rxg4F4sQilrauxt5mWPEbu342y+Px+OD4oVZ1yeF9NAAAgExIJBwl854p1bn87rVgam9J/3mYICCD5GhbBy6MEFODgMZXcSvJ8Q2g7S1Y0mro95mI2p+8WtPfxjUTzERemcduNa3eZu7+i1I1GwIDAQAB"

動作確認

実際にメールを送信した際のGmailにおける受信結果が以下となる。SPF、DKIM、DMARCがすべてPASSしていることがわかる。

以上で、DNSへレコード追加とPostfixへOpenDKIMを導入し、SPF、DMARC、DKIMに対応したメール送信環境を構築する手順は完了となる。

2024年1月21日日曜日

Raspberry Pi OSなどのDebian系OSのviで、方向キーによる移動やバックスペースを使えるようにする

Raspberry Pi OSなどのDebianベースのOSにおいてviを使う際に、入力モード(i または aを押して文字入力ができる状態)にした際に方向キーで移動しようとすると、ABCDの文字が入力されてしまう。

方向キーと文字の関係は以下の通り。
   A
   |
D -+- C
   |
   B
とても不便なので、viで方向キーを用いても通常通りカーソルを移動できるように設定する。併せてviでバックスペースも正常に使用できるように対処する。

設定手順

各ユーザーのホームディレクトリに.vimrcというファイルを作成し、以下2行の設定を書き込んでやればよい。
set nocompatible
set backspace=indent,eol,start
ワンライナーで設定したい場合は以下の通り実施する。すでに.vimrcがある可能性を考慮して、追記でリダイレクトしている。
$ echo -e "set nocompatible\nset backspace=indent,eol,start" >> ~/.vimrc
$ cat ~/.vimrc
set nocompatible
set backspace=indent,eol,start
これだけで、特に再ログイン等も不要で、次回vi実行時から設定が反映され、入力モードであっても方向キーが使えるようになる。

更新履歴

  • 2018/3/26 新規作成
  • 2024/1/21 補足情報を追記
2024年1月20日土曜日

Zabbix APIを使って未クローズの障害イベントをクローズする

Zabbixではトリガーによって検知した障害は、復旧条件を満たさなければクローズされることはなく、いつまでも障害として残り続けてしまう。

Zabbix 4.0以降では、管理画面から手動による障害のクローズができるようになったため、復旧条件の記載が難しいログ監視やSNMP Trap監視などにおいても、クローズができるようになりかなり便利になった。

とはいえ、大量の監視対象がある環境においては、障害のクローズを手作業で実施すること自体が負荷となるケースが存在すると考えられるため、今回はZabbix APIを使って未クローズの障害イベントを一気にクローズする手順を検証した。

環境

環境は以下の通りZabbix 5.0を使用している。ただし、Zabbix 4.0以降でも利用できることを確認するため、Zabbix APIのマニュアルはZabbix 4.0のものを確認して検証を行った。

  • OS : CentOS 8.1
  • Zabbix : 5.0.8
  • APIのURL : http://192.168.11.24/zabbix/api_jsonrpc.php

Zabbix APIを使用する際に、jqコマンドが必要となるため、事前にインストールをしておく。jqコマンドは、Zabbix APIやREST APIなどで利用されているJSON形式のデータを整形して出力したり、必要なデータのみ抽出するのに利用するコマンドとなる。

# dnf install epel-release
# dnf install jq

手順

Zabbix APIを実行する際に送信するJSON形式のデータは、以下構文となる。

{
  "jsonrpc": "2.0",
  "method": "メソッド名",
  "params": {
    "パラメータ1 Key": "パラメータ1 Value",
    "パラメータ2 Key": "パラメータ2 Value",
    ...
  },
  "auth": "Zabbix認証トークン",
  "id": 任意の数字
}

以降の手順では、上記構文の送信データを作成したのち、curlコマンドでそのデータを送信してAPIを実行する、という流れとなる。

また、Zabbix APIのURLやヘッダ情報などは、毎回同一となることから、あらかじめ変数として利用できるようにしておこう。

# header='Content-Type:application/json-rpc'
# apiurl='http://192.168.11.24/zabbix/api_jsonrpc.php'

1. Zabbix APIの認証トークンを取得

Zabbix APIの実行は、認証トークンが必要となる。認証トークンは、user.loginメソッドで取得できる。Zabbixの管理者権限を持つユーザとパスワードを指定して、以下のようにAPIを実行するためのデータを作成する。

# json='{"jsonrpc": "2.0","method": "user.login","params": {"user": "Admin","password": "XXXXXXXX"},"id": 1,"auth": null}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "user.login",
  "params": {
    "user": "Admin",
    "password": "XXXXXXXX"
  },
  "id": 1,
  "auth": null
}

認証トークンは、以降の作業で常に指定して利用することから、実行結果を変数$zbxauthに代入しておく。

# zbxauth=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r ".result")
# echo $zbxauth
e2519a6fbc680a87affc8382b65cdcb7

2. 未クローズの障害イベントを取得

障害イベントのクローズには、eventidが必要となる。problem.getメソッドを使うことで一覧として取得できるので、以下の通り送信データを作成する。

# json='{"jsonrpc": "2.0","method": "problem.get","params": {"output": "eventid"},"id": 3,"auth": "'$zbxauth'"}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "problem.get",
  "params": {
    "output": "eventid"
  },
  "id": 3,
  "auth": "e2519a6fbc680a87affc8382b65cdcb7"
}

取得したeventidは、次の手順で配列として利用できるようにするため、jqコマンドを使って出力結果を整形して変数$eventidsに格納する。以下コマンドの実行例では、6件の障害イベントが対象となる。

# eventids=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -c '[ .result[].eventid ]')
# echo $eventids
["34308","50073","50875","50876","50877","50954"]

3. 障害イベントを手動クローズ

イベントに対する捜査はevent.acknowledgeメソッドを使う。event.acknowledgeメソッドでは、イベントに対して実行する処理をactionにて指定するが、その際に指定できる値は以下の通りとなる。

  • 1 - close problem;
  • 2 - acknowledge event;
  • 4 - add message;
  • 8 - change severity.

上記は組み合わせることが可能であり、今回は「1 : 障害のクローズ」と「4 : メッセージ追加」の2つを同時に実行するため、actionを1 + 4 = 5で指定する。

# json='{"jsonrpc": "2.0", "method": "event.acknowledge", "params": {"eventids": '$eventids', "action": 5, "message": "Closed problem by API"}, "auth": "'$zbxauth'", "id": 1}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "event.acknowledge",
  "params": {
    "eventids": [
      "34308",
      "50073",
      "50875",
      "50876",
      "50877",
      "50954"
    ],
    "action": 5,
    "message": "Closed problem by API"
  },
  "auth": "e2519a6fbc680a87affc8382b65cdcb7",
  "id": 1
}

障害イベントのクローズ前に、GUI上でも障害一覧を確認しておく。1件はホストを無効化しているため障害が表示されていないが、GUI上では5件の障害が表示されている。後ほど、API実行後に赤枠で示したアラートがクローズされることを確認する。

curlにてAPIを実行する。resultに処理を実行したイベントIDの一覧が表示されればOKとなる。

# curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
{
  "jsonrpc": "2.0",
  "result": {
    "eventids": [
      34308,
      50073,
      50875,
      50876,
      50877,
      50954
    ]
  },
  "id": 1
}

GUI上にて再度障害一覧を確認すると、すべての障害がクローズされ、一覧から消えていることが確認できた。

また、クローズされたイベントを確認すると、APIで指定した「Closed problem by API」のメッセージとともに、ステータスが「解決済」になっていることが確認できた。

4. Zabbix APIの認証トークンを削除

最後に認証トークンを削除するため、user.logoutメソッドを使用する。

# json='{"jsonrpc": "2.0","method": "user.logout","params": [],"id": 4,"auth": "'$zbxauth'"}'
# echo $json | jq
{
  "jsonrpc": "2.0",
  "method": "user.logout",
  "params": [],
  "id": 4,
  "auth": "e2519a6fbc680a87affc8382b65cdcb7"
}

curlにてAPIを実行し、resulttrueであれば、認証トークンの削除が正常に実施されている。

# curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
{
  "jsonrpc": "2.0",
  "result": true,
  "id": 4
}

スクリプト化してみる

クローズ処理を定期的に実行できるようclose_zabbix_problem.shという名前のスクリプトを作成し、cronに登録して定期的に古い障害イベントをクローズできるようにしてみた。

7日前の未分類、情報、警告ステータスのイベントをクローズする処理となり、クローズする対象を限定するため、イベントID取得時に、time_tillseveritiesといったパラメータを指定している。

#!/bin/bash

# クローズ対象の日付
clode_date=$(date +%s --date '7 day ago')
close_message="Closed problem by API"

# Zabbix APIログイン
header='Content-Type:application/json-rpc'
apiurl='http://192.168.11.24/zabbix/api_jsonrpc.php'
json='{"jsonrpc": "2.0","method": "user.login","params": {"user": "Admin","password": "XXXXXXXX"},"id": 1,"auth": null}'
zbxauth=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r ".result")

# イベント一覧(未解決の障害、指定日以前のもの、未分類・情報・警告イベント)を取得
json='{"jsonrpc": "2.0","method": "problem.get","params": {"output": "eventid","time_till": '${clode_date}',"severities": [0,1,2]},"auth": "'${zbxauth}'","id": 1}'
eventids=$(curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq -r '.result[].eventid ' | tr '\n' ' ')

# Zabbix APIでイベントにメッセージ追加しクローズ
for eventid in ${eventids}; do
  json='{"jsonrpc": "2.0","method": "event.acknowledge","params": {"eventids": '${eventid}',"action": 5,"message": "'${close_message}'"},"auth": "'${zbxauth}'","id": 1}'
  curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq
done

# Zabbix APIログオフ
json='{"jsonrpc": "2.0","method": "user.logout","params": [],"id": 4,"auth": "'$zbxauth'"}'
curl -sS -X POST -H "${header}" -d "${json}" ${apiurl} | jq

exit 0

参考

更新履歴

  • 2021/2/17 新規作成
  • 2024/1/20 クローズ処理のスクリプト化の記載を追記

人気の投稿