zaki work log

作業ログやら生活ログやらなんやら

[Kubernetes] ヘルスチェックAPIを試す (bearer token認証付き)

KubernetesのヘルスチェックのAPIエンドポイントを試す。 といっても提供されているエンドポイントをcurl叩けばよいだけ。

kubernetes.io

現在は3つのエンドポイントが提供されているが、1つは非推奨とのこと。

  • healthz (非推奨)
  • livez
  • readyz

livezとreadyzの違いは、具体的な内訳までは書かれてないけど、liveness probeとreadyness probeの違いと同じと考えてよさそうで、livez=生きている・readyz=サービス提供可能と解釈してよさそう。(内部でクラスタやアプリが生きてるだけでよいか、外部からのアクセスに反応できるか、的な?)

kubectlを使ったヘルスチェック

$ kubectl get --raw='/readyz'
ok

冗長オプションもあり、?verboseを付加すると情報量が増える。
ドキュメントによると「人間がチェックするために使用するもの」とのこと。

$ kubectl get --raw='/readyz?verbose'
[+]ping ok
[+]log ok
[+]etcd ok
[+]etcd-readiness ok
[+]informer-sync ok
[+]poststarthook/start-kube-apiserver-admission-initializer ok
[+]poststarthook/generic-apiserver-start-informers ok
[+]poststarthook/priority-and-fairness-config-consumer ok
[+]poststarthook/priority-and-fairness-filter ok
[+]poststarthook/storage-object-count-tracker-hook ok
[+]poststarthook/start-apiextensions-informers ok
[+]poststarthook/start-apiextensions-controllers ok
[+]poststarthook/crd-informer-synced ok
[+]poststarthook/start-service-ip-repair-controllers ok
[+]poststarthook/rbac/bootstrap-roles ok
[+]poststarthook/scheduling/bootstrap-system-priority-classes ok
[+]poststarthook/priority-and-fairness-config-producer ok
[+]poststarthook/start-system-namespaces-controller ok
[+]poststarthook/bootstrap-controller ok
[+]poststarthook/start-cluster-authentication-info-controller ok
[+]poststarthook/start-kube-apiserver-identity-lease-controller ok
[+]poststarthook/start-deprecated-kube-apiserver-identity-lease-garbage-collector ok
[+]poststarthook/start-kube-apiserver-identity-lease-garbage-collector ok
[+]poststarthook/start-legacy-token-tracking-controller ok
[+]poststarthook/aggregator-reload-proxy-client-cert ok
[+]poststarthook/start-kube-aggregator-informers ok
[+]poststarthook/apiservice-registration-controller ok
[+]poststarthook/apiservice-status-available-controller ok
[+]poststarthook/kube-apiserver-autoregistration ok
[+]autoregister-completion ok
[+]poststarthook/apiservice-openapi-controller ok
[+]poststarthook/apiservice-openapiv3-controller ok
[+]poststarthook/apiservice-discovery-controller ok
[+]shutdown ok
readyz check passed

curlでヘルスチェック

APIサーバー(通常kubectl cluster-infoとかで確認できる接続先サーバー)の/livez/readyzにアクセスする。
以下readyzの場合

$ curl -k 'https://192.168.0.131:6443/readyz'
ok

こんな感じでkubectl get --raw='/readyzと同じ応答が返る。
kubectlの場合と同じく、?verboseを付与すれば情報量が増える。

認証が必要な場合

外部の監視ツールなどからHTTPSアクセスで普通に使えるはずだけど、Kubernetesディストリビューションによっては認証が必要な場合があるっぽい。
たとえばK3sのクラスター(v1.28.4)だと認証エラーになった。

$ curl -k 'https://192.168.0.75:6443/readyz'
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}

この場合はdefault Service Accountのトークンを使った認証が簡単で、kubectl create tokenで生成できる。

$ export K8S_TOKEN=$(kubectl create token default -n default)
$ curl -k -H "Authorization: Bearer $K8S_TOKEN" 'https://192.168.0.75:6443/readyz'
ok

kubectl create token以外では、トークン用のSecretリソースを作成してもOK

まず以下のリソースを作成して

apiVersion: v1
kind: Secret
metadata:
  name: default-token
  annotations:
    kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token

$ SECRET_TOKEN=$(kubectl get secret default-token -o jsonpath='{.data.token}' | base64 --decode)
$ curl -k  -H "Authorization: Bearer $SECRET_TOKEN" 'https://192.168.0.75:6443/readyz'
ok

といった感じ。

zaki-hmkc.hatenablog.com

kubernetes.io


ただ、どういう状況の時にOKではなくなるのかが不明…
CoreDNSを落としたり、workerノードをシャットダウンしたりしても特に問題なかった。

情報をお持ちの方、ぜひ教えてください。

環境

  • 192.168.0.131 (kubeadm構築): 1.28.2
  • 192.168.0.75 (k3s構築): 1.28.4

Proxmox VEのweb管理画面にCPUの温度を表示する

去年の春に構築して灼熱の夏は無事に乗り越えたMINISFORUM NAB6だけど、以前使ってたIntel NUC(現在win11が入ってる)はちょいちょい熱暴走起こしてたので、念のために簡単にモニタリングできるようにしてみた。

2024.04.01 パッケージバージョンアップで変更が復元される件を追記

lm-sensorsのインストール

lm-sensorsはハードでモニタリングしている情報をLinuxで取得する古来からあるプログラム。Debianパッケージでも提供されているのでそれを使用する。

atmarkit.itmedia.co.jp

apt-get install lm-sensors

aptでシュッと入る。

root@pve:~# apt-get install lm-sensors
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following package was automatically installed and is no longer required:
  proxmox-kernel-6.2.16-15-pve
Use 'apt autoremove' to remove it.
The following additional packages will be installed:
  libsensors-config libsensors5
Suggested packages:
  fancontrol read-edid i2c-tools
The following NEW packages will be installed:
  libsensors-config libsensors5 lm-sensors
0 upgraded, 3 newly installed, 0 to remove and 1 not upgraded.
Need to get 146 kB of archives.
After this operation, 518 kB of additional disk space will be used.
Do you want to continue? [Y/n] y
Get:1 http://ftp.jp.debian.org/debian bookworm/main amd64 libsensors-config all 1:3.6.0-7.1 [14.3 kB]
Get:2 http://ftp.jp.debian.org/debian bookworm/main amd64 libsensors5 amd64 1:3.6.0-7.1 [34.2 kB]
Get:3 http://ftp.jp.debian.org/debian bookworm/main amd64 lm-sensors amd64 1:3.6.0-7.1 [97.2 kB]
Fetched 146 kB in 1s (177 kB/s)      
Selecting previously unselected package libsensors-config.
(Reading database ... 77682 files and directories currently installed.)
Preparing to unpack .../libsensors-config_1%3a3.6.0-7.1_all.deb ...
Unpacking libsensors-config (1:3.6.0-7.1) ...
Selecting previously unselected package libsensors5:amd64.
Preparing to unpack .../libsensors5_1%3a3.6.0-7.1_amd64.deb ...
Unpacking libsensors5:amd64 (1:3.6.0-7.1) ...
Selecting previously unselected package lm-sensors.
Preparing to unpack .../lm-sensors_1%3a3.6.0-7.1_amd64.deb ...
Unpacking lm-sensors (1:3.6.0-7.1) ...
Setting up libsensors-config (1:3.6.0-7.1) ...
Setting up libsensors5:amd64 (1:3.6.0-7.1) ...
Setting up lm-sensors (1:3.6.0-7.1) ...
Created symlink /etc/systemd/system/multi-user.target.wants/lm-sensors.service → /lib/systemd/system/lm-sensors.service.
Processing triggers for man-db (2.11.2-2) ...
Processing triggers for libc-bin (2.36-9+deb12u4) ...
root@pve:~# 

動作の確認

手元の環境では設定なしでsensorsコマンド実行で温度が取れる。

root@pve:~# sensors
coretemp-isa-0000
Adapter: ISA adapter
Package id 0:  +46.0°C  (high = +100.0°C, crit = +100.0°C)
Core 0:        +36.0°C  (high = +100.0°C, crit = +100.0°C)
Core 4:        +46.0°C  (high = +100.0°C, crit = +100.0°C)
Core 8:        +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 12:       +37.0°C  (high = +100.0°C, crit = +100.0°C)
Core 16:       +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 20:       +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 28:       +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 29:       +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 30:       +38.0°C  (high = +100.0°C, crit = +100.0°C)
Core 31:       +38.0°C  (high = +100.0°C, crit = +100.0°C)

acpitz-acpi-0
Adapter: ACPI interface
temp1:        +27.8°C  (crit = +105.0°C)

nvme-pci-0100
Adapter: PCI adapter
Composite:    +46.9°C  (low  =  -0.1°C, high = +82.8°C)
                       (crit = +89.8°C)

root@pve:~# 

-jオプションを付与するとJSON形式で出力できる。

root@pve:~# sensors -j 
{
   "coretemp-isa-0000":{
      "Adapter": "ISA adapter",
      "Package id 0":{
         "temp1_input": 41.000,
         "temp1_max": 100.000,
         "temp1_crit": 100.000,
         "temp1_crit_alarm": 0.000
      },
      "Core 0":{

    ...

動作しているマシンのCPUはCore i7-12650Hで、物理CPUは1、CPUごとのコアは10。「Core N」のNの値が飛び飛びなのが気になるけど、10コアそれぞれの温度と、「Package id 0」は全体の加重平均らしいので、この値を表示させてみる。

askubuntu.com

www.intel.com

UIの改造

温度を取れるようになったので、PVEのweb画面に表示してみる。
参考情報はこちらというか、基本この通りやれば良い。

www.forum-nas.fr

値の取得部分

Perlモジュールを修正。

--- /usr/share/perl5/PVE/API2/Nodes.pm.org      2023-12-14 23:20:06.000000000 +0900
+++ /usr/share/perl5/PVE/API2/Nodes.pm  2024-02-04 11:25:27.877690336 +0900
@@ -446,6 +446,10 @@
        $res->{pveversion} = PVE::pvecfg::package() . "/" .
            PVE::pvecfg::version_text();
 
+        # for lm-sensors
+        # https://www.forum-nas.fr/threads/tuto-afficher-la-temp%C3%A9rature-cpu-dans-proxmox-ve-8.21096/
+        $res->{thermalstate} = `sensors -j`;
+
        my $dinfo = df('/', 1);     # output is bytes
 
        $res->{rootfs} = {

UI部分

参考リンクはPVE Manager Versionとあるが、現バージョンだとManager Versionになってるのでそれを探す。

    items: [

        //...

        {
            itemId: 'version',
            colspan: 2,
            printBar: false,
            title: gettext('Manager Version'),
            textField: 'pveversion',
            value: '',
        },
    ],

    updateTitle: function() {

このitemsリストに以下のPerlモジュールで収集したデータを取得して返すコードを追加。
このとき辞書のキーはsensors -jで確認できるJSONのキーを使用する。

--- /usr/share/pve-manager/js/pvemanagerlib.js.org      2023-12-14 23:20:06.000000000 +0900
+++ /usr/share/pve-manager/js/pvemanagerlib.js  2024-02-04 11:59:06.069857992 +0900
@@ -43294,6 +43294,16 @@
            textField: 'pveversion',
            value: '',
        },
+        {
+            itemId: 'thermal',
+            colspan: 2,
+            printBar: false,
+            title: gettext('CPU Thermal State'),
+            textField: 'thermalstate',
+            renderer: function(value) {
+                return `${JSON.parse(value)["coretemp-isa-0000"]["Package id 0"]["temp1_input"]}°C`;
+            }
+        }
     ],
 
     updateTitle: function() {

手元のマシンはコアが10個あってそれぞれの値を並べるのは表示領域的に厳しいので「Package id 0」を1個だけ使用。

表示を確認

変更が済んだらWeb UIサービスを再起動する。

systemctl restart pveproxy

これで、webの画面で該当のpveノードのサマリーで温度を確認できる。

応用すれば何でも表示できると思う。
見た感じ1秒に1回のペースで値が更新されるので、そのたびに内部の取得コマンドが実行されてるのだと思われ。

/usr/share以下のファイルって勝手に書き換えていいの?

どちらのファイルもpve-managerパッケージによってインストールされているので、バージョンアップによって変更(復元)される可能性はあるのでその点は注意。(バージョンアップで復元されました)

root@pve:~# dpkg -S /usr/share/perl5/PVE/API2/Nodes.pm /usr/share/pve-manager/js/pvemanagerlib.js
pve-manager: /usr/share/perl5/PVE/API2/Nodes.pm
pve-manager: /usr/share/pve-manager/js/pvemanagerlib.js

セルフホスト版GitLabの初期パスワード未設定時の設定方法が変わっていたのでメモ

年末にGitLabのコンテナレジストリが必要になったので久しぶりにローカルに新しくver16.6をDocker Composeで構築したら、初回アクセス時のrootの初期パスワード設定を行う画面が表示されることなくログイン画面に遷移してどうすればよいかわからず戸惑ったため、GitLabの初期パスワードについて簡単にまとめ。

調べた感じでは、rootの初期パスワードの「未設定の場合は初回起動時にランダムにセットされる」という動作はver14で導入された模様。

gitlab.com

gitlab.com

デプロイ後の初期パスワードの確認

rootの初期パスワードを未設定でデプロイした場合、コンテナ内の/etc/gitlab/initial_root_passwordに生成された初期パスワードが保存される。
下記の場合、初期パスワードはnL4VngugEu5VqoxQUNcZPE5ke284JuOZd6N1r0EtmNE=になる。

$ docker compose exec gitlab-ce.jp-z.jp cat /etc/gitlab/initial_root_password
# WARNING: This value is valid only in the following conditions
#          1. If provided manually (either via `GITLAB_ROOT_PASSWORD` environment variable or via `gitlab_rails['initial_root_password']` setting in `gitlab.rb`, it was provided before database was seeded for the first time (usually, the first reconfigure run).
#          2. Password hasn't been changed manually, either via UI or via command line.
#
#          If the password shown here doesn't work, you must reset the admin password following https://docs.gitlab.com/ee/security/reset_user_password.html#reset-your-root-password.

Password: nL4VngugEu5VqoxQUNcZPE5ke284JuOZd6N1r0EtmNE=

# NOTE: This file will be automatically deleted in the first reconfigure run after 24 hours.

ただし注意点として、このファイルはGitLab初回起動から24時間経過後の再起動時(だよね?24時間経過で消える、ではなく)に消去されるので注意。(ホストOSにコピーなどしておこう)

The password file is automatically deleted in the first container restart after 24 hours.

docs.gitlab.com

初期パスワードを最初から設定する

環境変数 GITLAB_ROOT_PASSWORD にセットしてデプロイする。
composeファイルにするとこんな感じ。

    environment:
      GITLAB_OMNIBUS_CONFIG: "external_url 'https://gitlab-ce.jp-z.jp:8443'; nginx['listen_port']=8443; gitlab_rails['gitlab_shell_ssh_port'] = 25022; gitlab_rails['registry_enabled']=true; registry_external_url 'https://gitlab-ce.jp-z.jp:25000'"
      GITLAB_ROOT_PASSWORD: curry_tabetai

この設定でデプロイされたGitLabは、rootの初期パスワードがcurry_tabetaiになる。
(この場合/etc/gitlab/initial_root_passwordファイルは作成されない)

環境変数以外でも/etc/gitlab/gitlab.rbの設定でいけるっぽい(未確認)

gitlab_rails['initial_root_password'] = '<my_strong_password>'

パスワードリセット

どうしてもパスワードがわからない場合は、シェルを起動してコマンドを使ったリセットは可能。

$ docker compose exec gitlab-ce.jp-z.jp bash
root@gitlab-ce:/# gitlab-rake "gitlab:password:reset"
Enter username: root
Enter password: 
Confirm password: 
Password successfully updated for user with username root.
root@gitlab-ce:/# 

これで新しくセットしたパスワードでログインできる。
(ユーザー名も指定できるのでどのユーザーのパスワードでもリセットできるはず)


いやぁ、、ver14って2年半前にリリースされたバージョンみたい。ずいぶん使ってなかったんだな。。

about.gitlab.com

Composeファイル

使ったComposeファイルは以下。
事前に /opt/gitlab-cert に証明書ファイルを作成しておく。

---
services:
  gitlab-ce.jp-z.jp:
    image: gitlab/gitlab-ce:16.8.1-ce.0
    hostname: gitlab-ce.jp-z.jp
    restart: always
    ports:
      - "8443:8443"
      - "25000:25000"
      - "25022:22"
    environment:
      GITLAB_OMNIBUS_CONFIG: "external_url 'https://gitlab-ce.jp-z.jp:8443'; nginx['listen_port']=8443; gitlab_rails['gitlab_shell_ssh_port'] = 25022; gitlab_rails['registry_enabled']=true; registry_external_url 'https://gitlab-ce.jp-z.jp:25000'"
      GITLAB_ROOT_PASSWORD: curry_tabetai
    volumes:
      - /opt/gitlab-tmpdata/config:/etc/gitlab:Z
      - /opt/gitlab-tmpdata/logs:/var/log/gitlab:Z
      - /opt/gitlab-tmpdata/data:/var/opt/gitlab:Z
      - /opt/gitlab-tmpdata/registry:/var/opt/gitlab/gitlab-rails/shared/registry:Z
      - /opt/gitlab-cert:/etc/gitlab/ssl
    networks:
      - gitlab-network
  gitlab-runner:
    image: gitlab/gitlab-runner:v16.7.1
    restart: always
    volumes:
      - /opt/gitlab-runner/config:/etc/gitlab-runner:Z
      - /var/run/docker.sock:/var/run/docker.sock
      - /opt/gitlab-cert:/etc/gitlab-runner/certs
    networks:
      - gitlab-network

networks:
  gitlab-network:

GitLab with Docker Compose関連エントリ

zaki-hmkc.hatenablog.com

zaki-hmkc.hatenablog.com

[Kubernetes] K3sにおける証明書の期限の延長や設定(平たく言うと塩漬け運用したい場合のx年設定)について

オンプレKubernetesにおいて選択肢の一つになりうるK3sで内部で使用する証明書の期限についての調査&検証のまとめ的な作業ログ
確認した環境のホストOSはUbuntu 22.04とFedora39でK3sは1.28.5

デフォルト設定

K3sにおいて証明書は構築のタイミングで生成され1年が有効期限になっている。
機能的には期限まで90日になると「K3s起動時に」自動更新されるようになっている。

K3s client and server certificates are valid for 365 days from their date of issuance. Any certificates that are expired, or within 90 days of expiring, are automatically renewed every time K3s starts.

https://docs.k3s.io/cli/certificate

期限が切れてしまうと各操作でx509のエラーが発生する。 (期限が切れてしまったあとの再起動でも期限は更新される)

root@ubuntu-dev01:~# kubectl get node
E1027 00:00:03.561891   36698 memcache.go:265] couldn't get current server API group list: Get "https://127.0.0.1:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z
E1027 00:00:03.562512   36698 memcache.go:265] couldn't get current server API group list: Get "https://127.0.0.1:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z
E1027 00:00:03.564188   36698 memcache.go:265] couldn't get current server API group list: Get "https://127.0.0.1:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z
E1027 00:00:03.564776   36698 memcache.go:265] couldn't get current server API group list: Get "https://127.0.0.1:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z
E1027 00:00:03.566360   36698 memcache.go:265] couldn't get current server API group list: Get "https://127.0.0.1:6443/api?timeout=32s": tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z
Unable to connect to the server: tls: failed to verify certificate: x509: certificate has expired or is not yet valid: current time 2025-10-27T00:00:03Z is after 2025-10-26T00:00:05Z

自動ローテートの確認

初期状態

root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep Validity -A2
        Validity
            Not Before: Jan 24 00:40:08 2024 GMT
            Not After : Jan 23 00:40:08 2025 GMT

期限切れ90日前に設定して再起動しても、以下の通り変化なし。

root@ubuntu-dev01:~# timedatectl set-time "2024-10-25"
root@ubuntu-dev01:~# date
Thu Oct 25 00:00:15 UTC 2024
root@ubuntu-dev01:~# systemctl restart k3s
root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep Validity -A2
        Validity
            Not Before: Jan 24 00:40:08 2024 GMT
            Not After : Jan 23 00:40:08 2025 GMT

期限切れ89日前に設定に設定して再起動すると、以下の通り更新される。

root@ubuntu-dev01:~# timedatectl set-time "2024-10-26"
root@ubuntu-dev01:~# date
Sat Oct 26 00:00:56 UTC 2024
root@ubuntu-dev01:~# systemctl restart k3s
root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep Validity -A2
        Validity
            Not Before: Jan 24 00:40:08 2024 GMT
            Not After : Oct 26 00:00:05 2025 GMT

この通り「起動時の1年後」に更新された。

手動ローテート

期限が近くなる前でも、手動での強制ローテートは可能。
その場合でもK3sの再起動は必要。

証明書のローテートを行うには以下のコマンドを実行。

k3s certificate rotate

具体的な手順と動きについて確認してみる。
事前の証明書ファイル群はこの通り。

root@ubuntu-dev01:~# ls -l /var/lib/rancher/k3s/server/tls/*.crt
-rw-r--r-- 1 root root 1173 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-admin.crt
-rw-r--r-- 1 root root 1182 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-auth-proxy.crt
-rw-r--r-- 1 root root  566 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-ca.crt
-rw-r--r-- 1 root root  566 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-ca.nochain.crt
-rw-r--r-- 1 root root 1161 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-controller.crt
-rw-r--r-- 1 root root 1157 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-k3s-cloud-controller.crt
-rw-r--r-- 1 root root 1149 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-k3s-controller.crt
-rw-r--r-- 1 root root 1177 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt
-rw-r--r-- 1 root root 1145 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-kube-proxy.crt
-rw-r--r-- 1 root root 1149 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-scheduler.crt
-rw-r--r-- 1 root root 1181 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-supervisor.crt
-rw-r--r-- 1 root root  591 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/request-header-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/server-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/server-ca.nochain.crt
-rw-r--r-- 1 root root 1400 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/serving-kube-apiserver.crt
root@ubuntu-dev01:~#
root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep
 Validity -A2
        Validity
            Not Before: Jan 24 01:12:19 2024 GMT
            Not After : Jan 23 01:12:19 2025 GMT

ローテートのコマンドを実行すると証明書ファイルは消えてしまうので、安全のためにまずK3sを停止する。

root@ubuntu-dev01:~# systemctl stop k3s
root@ubuntu-dev01:~# 

ローテートのコマンドを実行。

root@ubuntu-dev01:~# k3s certificate rotate
INFO[0000] Server detected, rotating server certificates 
INFO[0000] Rotating certificates for admin service      
INFO[0000] Rotating certificates for etcd service       
INFO[0000] Rotating certificates for api-server service 
INFO[0000] Rotating certificates for controller-manager service 
INFO[0000] Rotating certificates for cloud-controller service 
INFO[0000] Rotating certificates for scheduler service  
INFO[0000] Rotating certificates for k3s-server service 
INFO[0000] Rotating dynamic listener certificate        
INFO[0000] Rotating certificates for k3s-controller service 
INFO[0000] Rotating certificates for auth-proxy service 
INFO[0000] Rotating certificates for kubelet service    
INFO[0000] Rotating certificates for kube-proxy service 
INFO[0000] Successfully backed up certificates for all services to path /var/lib/rancher/k3s/server/tls-1706059051, please restart k3s server or agent to rotate certificates 
root@ubuntu-dev01:~# 

すると、CA以外の証明書ファイルは削除される。

root@ubuntu-dev01:~# systemctl start k3s
root@ubuntu-dev01:~# 
root@ubuntu-dev01:~# ls -l /var/lib/rancher/k3s/server/tls/*.crt
-rw-r--r-- 1 root root  566 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-ca.crt
-rw-r--r-- 1 root root  566 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-ca.nochain.crt
-rw-r--r-- 1 root root 1181 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/client-supervisor.crt
-rw-r--r-- 1 root root  591 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/request-header-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/server-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:12 /var/lib/rancher/k3s/server/tls/server-ca.nochain.crt

この状態でK3sを起動すると、証明書ファイルが生成される。

root@ubuntu-dev01:~# systemctl restart k3s
root@ubuntu-dev01:~# 
root@ubuntu-dev01:~# 
root@ubuntu-dev01:~# ls -l /var/lib/rancher/k3s/server/tls/*.crt
-rw-r--r-- 1 root root 1173 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-admin.crt
-rw-r--r-- 1 root root 1178 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-auth-proxy.crt
-rw-r--r-- 1 root root  570 Jan 24 01:14 /var/lib/rancher/k3s/server/tls/client-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-ca.nochain.crt
-rw-r--r-- 1 root root 1165 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-controller.crt
-rw-r--r-- 1 root root 1165 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-k3s-cloud-controller.crt
-rw-r--r-- 1 root root 1153 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-k3s-controller.crt
-rw-r--r-- 1 root root 1181 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt
-rw-r--r-- 1 root root 1144 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-kube-proxy.crt
-rw-r--r-- 1 root root 1153 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/client-scheduler.crt
-rw-r--r-- 1 root root 1185 Jan 24 01:14 /var/lib/rancher/k3s/server/tls/client-supervisor.crt
-rw-r--r-- 1 root root  591 Jan 24 01:14 /var/lib/rancher/k3s/server/tls/request-header-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:14 /var/lib/rancher/k3s/server/tls/server-ca.crt
-rw-r--r-- 1 root root  570 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/server-ca.nochain.crt
-rw-r--r-- 1 root root 1400 Jan 24 01:30 /var/lib/rancher/k3s/server/tls/serving-kube-apiserver.crt

この通り起動のタイミング基準の期限で証明書が作成された。

root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep
 Validity -A2
        Validity
            Not Before: Jan 24 01:12:19 2024 GMT
            Not After : Jan 23 01:18:31 2025 GMT

期限のカスタム

K3s自体の機能には証明書の期限を設定する機能は意外(?)と無い。
ただし、以下のissueで議論はされており、K3sの機能・設定としては提供されてないが、K3s内で使用されているライブラリが環境変数から有効期限を参照する仕組みになっている。

github.com

ただ、K3s自体のインストールで使用する環境変数ではないため、 以下のようにK3sインストール時の環境変数指定を付加した実行では反映されない。

curl -sfL https://get.k3s.io | CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS=750 sh -

証明書期限を設定した環境変数を認識させるには、事前に「k3sの実行時に環境変数として認識させる」必要がある。
具体的には以下の内容のファイルを /etc/default/k3s にK3sインストール前に作成しておけば、K3sインストール時に認識して指定期限の証明書を生成する。

root@ubuntu-dev01:~# cat /etc/default/k3s 
CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS=3650
root@ubuntu-dev01:~#
root@ubuntu-dev01:~# date
Wed Jan 24 01:47:34 UTC 2024
root@ubuntu-dev01:~#
root@ubuntu-dev01:~# curl -sfL https://get.k3s.io | sh -
[INFO]  Finding release for channel stable
[INFO]  Using v1.28.5+k3s1 as release
:
:
[INFO]  systemd: Starting k3s
root@ubuntu-dev01:~# 
root@ubuntu-dev01:~# openssl x509 -text -noout -in /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | grep
 Validity -A2
        Validity
            Not Before: Jan 24 01:48:05 2024 GMT
            Not After : Jan 21 01:48:05 2034 GMT

この通り、10年後の期限に設定された。

コンテナ版の場合

ローテート

90日の件は非コンテナ版と同じなので割愛。
ここでは強制ローテートについて。

VM版と異なり「K3s停止時に実行」ができないため、K3s起動中(コンテナ起動中)にローテート処理を行い、そのあとに再起動を行う。

[zaki@cloud-dev2 k3s-compose]$ docker compose ps
NAME                   IMAGE                COMMAND             SERVICE   CREATED         STATUS         PORTS
k3s-compose-agent-1    rancher/k3s:latest   "/bin/k3s agent"    agent     2 minutes ago   Up 2 minutes   
k3s-compose-server-1   rancher/k3s:latest   "/bin/k3s server"   server    2 minutes ago   Up 2 minutes   0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp, 0.0.0.0:6443->6443/tcp, :::6443->6443/tcp

証明書の確認。

[zaki@cloud-dev2 k3s-compose]$ docker compose exec server sh
/ # 
/ # ls -l /var/lib/rancher/k3s/server/tls/
total 116
-rw-r--r-- 1 0 0 1169 Jan 23 20:16 client-admin.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-admin.key
-rw-r--r-- 1 0 0 1178 Jan 23 20:16 client-auth-proxy.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-auth-proxy.key
-rw-r--r-- 1 0 0  566 Jan 23 20:16 client-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-ca.key
-rw-r--r-- 1 0 0 1161 Jan 23 20:16 client-controller.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-controller.key
-rw-r--r-- 1 0 0 1157 Jan 23 20:16 client-k3s-cloud-controller.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-k3s-cloud-controller.key
-rw-r--r-- 1 0 0 1149 Jan 23 20:16 client-k3s-controller.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-k3s-controller.key
-rw-r--r-- 1 0 0 1177 Jan 23 20:16 client-kube-apiserver.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-kube-apiserver.key
-rw-r--r-- 1 0 0 1145 Jan 23 20:16 client-kube-proxy.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-kube-proxy.key
-rw------- 1 0 0  227 Jan 23 20:16 client-kubelet.key
-rw-r--r-- 1 0 0 1149 Jan 23 20:16 client-scheduler.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-scheduler.key
-rw-r--r-- 1 0 0 3815 Jan 23 20:16 dynamic-cert.json
drwxr-xr-x 2 0 0 4096 Jan 23 20:16 etcd
-rw-r--r-- 1 0 0  591 Jan 23 20:16 request-header-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 request-header-ca.key
-rw-r--r-- 1 0 0  570 Jan 23 20:16 server-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 server-ca.key
-rw------- 1 0 0 1675 Jan 23 20:16 service.key
-rw-r--r-- 1 0 0 1372 Jan 23 20:16 serving-kube-apiserver.crt
-rw------- 1 0 0  227 Jan 23 20:16 serving-kube-apiserver.key
-rw------- 1 0 0  227 Jan 23 20:16 serving-kubelet.key
drwx------ 2 0 0   84 Jan 23 20:16 temporary-certs
/ # 

ローテート要求処理の実行。
(ブログ用にシェルを起動してその中で実行してるが、docker compose execの引数で実行してもよい)

/ # k3s certificate rotate
INFO[0000] Server detected, rotating server certificates 
INFO[0000] Rotating certificates for admin service      
INFO[0000] Rotating certificates for etcd service       
INFO[0000] Rotating certificates for api-server service 
INFO[0000] Rotating certificates for controller-manager service 
INFO[0000] Rotating certificates for cloud-controller service 
INFO[0000] Rotating certificates for scheduler service  
INFO[0000] Rotating certificates for k3s-server service 
INFO[0000] Rotating dynamic listener certificate        
INFO[0000] Rotating certificates for k3s-controller service 
INFO[0000] Rotating certificates for auth-proxy service 
INFO[0000] Rotating certificates for kubelet service    
INFO[0000] Rotating certificates for kube-proxy service 
INFO[0000] Successfully backed up certificates for all services to path /var/lib/rancher/k3s/server/tls-1706041327, please restart k3s server or agent to rotate certificates 

CA以外のファイルが消えるのを確認。

/ # ls -l /var/lib/rancher/k3s/server/tls/
total 32
-rw-r--r-- 1 0 0  566 Jan 23 20:16 client-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-ca.key
-rw------- 1 0 0    0 Jan 23 20:22 dynamic-cert-regenerate
-rw-r--r-- 1 0 0 3815 Jan 23 20:16 dynamic-cert.json
drwxr-xr-x 2 0 0   86 Jan 23 20:22 etcd
-rw-r--r-- 1 0 0  591 Jan 23 20:16 request-header-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 request-header-ca.key
-rw-r--r-- 1 0 0  570 Jan 23 20:16 server-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 server-ca.key
-rw------- 1 0 0 1675 Jan 23 20:16 service.key
drwx------ 2 0 0   84 Jan 23 20:16 temporary-certs

K3sコンテナをリスタートする。

[zaki@cloud-dev2 k3s-compose]$ docker compose restart
[+] Restarting 2/2
 ✔ Container k3s-compose-agent-1   Started                                                    1.1s 
 ✔ Container k3s-compose-server-1  Started                                                    1.2s 
[zaki@cloud-dev2 k3s-compose]$ 

再作成されたのを確認。

[zaki@cloud-dev2 k3s-compose]$ docker compose exec server ls -l /var/lib/rancher/k3s/server/tls/
total 116
-rw-r--r-- 1 0 0 1169 Jan 23 20:25 client-admin.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-admin.key
-rw-r--r-- 1 0 0 1178 Jan 23 20:25 client-auth-proxy.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-auth-proxy.key
-rw-r--r-- 1 0 0  566 Jan 23 20:16 client-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 client-ca.key
-rw-r--r-- 1 0 0 1161 Jan 23 20:25 client-controller.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-controller.key
-rw-r--r-- 1 0 0 1157 Jan 23 20:25 client-k3s-cloud-controller.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-k3s-cloud-controller.key
-rw-r--r-- 1 0 0 1149 Jan 23 20:25 client-k3s-controller.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-k3s-controller.key
-rw-r--r-- 1 0 0 1177 Jan 23 20:25 client-kube-apiserver.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-kube-apiserver.key
-rw-r--r-- 1 0 0 1145 Jan 23 20:25 client-kube-proxy.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-kube-proxy.key
-rw------- 1 0 0  227 Jan 23 20:25 client-kubelet.key
-rw-r--r-- 1 0 0 1149 Jan 23 20:25 client-scheduler.crt
-rw------- 1 0 0  227 Jan 23 20:25 client-scheduler.key
-rw-r--r-- 1 0 0 3815 Jan 23 20:25 dynamic-cert.json
drwxr-xr-x 2 0 0 4096 Jan 23 20:25 etcd
-rw-r--r-- 1 0 0  591 Jan 23 20:16 request-header-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 request-header-ca.key
-rw-r--r-- 1 0 0  570 Jan 23 20:16 server-ca.crt
-rw------- 1 0 0  227 Jan 23 20:16 server-ca.key
-rw------- 1 0 0 1675 Jan 23 20:16 service.key
-rw-r--r-- 1 0 0 1376 Jan 23 20:25 serving-kube-apiserver.crt
-rw------- 1 0 0  227 Jan 23 20:25 serving-kube-apiserver.key
-rw------- 1 0 0  227 Jan 23 20:25 serving-kubelet.key
drwx------ 2 0 0   84 Jan 23 20:16 temporary-certs

期限のカスタム

Composeファイル内のserver環境変数に以下を追加。

    environment:
    - K3S_TOKEN=${K3S_TOKEN:?err}
    - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
    - K3S_KUBECONFIG_MODE=666
    - CATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYS=750        # これ

このComposeファイルでコンテナを起動し、期限を確認。

[zaki@cloud-dev2 k3s-compose]$ docker compose exec server cat /var/lib/rancher/k3s/server/tls/client-kube-apiserver.crt | openssl x509 -text -noout | grep Validity -A2
        Validity
            Not Before: Jan 23 20:54:48 2024 GMT
            Not After : Feb 11 20:54:48 2026 GMT

ちなみに環境変数の変更はrestart(stopstart)では反映されないので、初回起動時以外の変更はdownupk3s certificate rotateによる再作成が必要。


K3s自体には起動時の自動更新処理自体は備わってるけど実際にを運用で使うケースを考えると、オンプレでかつ一度構築したら様々な事情でメンテフリーで使いたい(運用に証明書更新を目的とした再起動も入れたくない)ということが多々あり、そうすると「証明書期限を可能な限り伸ばして初期構築したい」は自然な流れかなぁと思う。

メンテフリーというか、バージョン塩漬けというか。

ただしCATTLE_NEW_SIGNED_CERT_EXPIRATION_DAYSを使った期間の指定はK3s的には仕様に規定されてないはずなので、予告なく動きが変更される可能性もあるかも。

[Ansible / AWX / AAP] ログアグリゲーター設定でジョブの実行ログをLogstash経由でsyslogサーバーへ転送する

ロギング設定にログアグリゲーターを指定して、リモートのsyslogへジョブの実行履歴を転送してみます。

通常AWX・AAPではDBに記録されたジョブの実行履歴などはスケジュールのCleanup Job Scheduleで設定された期限が過ぎたら削除されるので、基本的にはUIで参照したりAPIで取得する必要があります。
そこでログのアグリゲーター機能を使ってログをリモート転送することで、(転送先でログの監視と通知を行うことで)リアルタイムでエラーに対するアラートを上げたりできるようになります。
(画面でジョブ起動するだけなら不要だと思うけど、スケジュール機能でバックグラウンドで動作させたりする場合はあぐりゲーターなしだと一定間隔でAPIを叩いたりして見に行く必要がある)

AWXで指定可能なログアグリゲーターは以下。

  • Logstash
  • Splunk
  • Loggly
  • Sumo Logic
  • other (詳細不明)

本エントリではELKスタックの一つとして利用されるLogstashをAWXをデプロイしているKubernetesクラスタ上へでHelmを使ってデプロイし、プラグインのsyslog outputでリモートのsyslogサーバーにジョブ履歴を転送してみます。
※ 本記事では機能的に疎通を確認してるレベルまでで、チューニングとかはやってません。

www.elastic.co

www.elastic.co

Syslogプラグイン入りLogstashイメージのビルド

LogstashをK8sに標準構成で一度デプロイしたあとに気づいたんですが、デフォルトではSyslog output pluginは含まれていません。

Installation

For plugins not bundled by default, it is easy to install by running bin/logstash-plugin install logstash-output-syslog.

https://www.elastic.co/guide/en/logstash/current/plugins-outputs-syslog.html#_installation_49

www.elastic.co

なので、Syslog output pluginを入れたコンテナイメージを作成します。

FROM docker.elastic.co/logstash/logstash:8.5.1

RUN bin/logstash-plugin install logstash-output-syslog

イメージをビルドし、使えるレジストリへpush

docker build . -t zakihmkc/logstash-syslog:8.5.1
docker push zakihmkc/logstash-syslog:8.5.1 

一応ここに置いてます。
https://hub.docker.com/r/zakihmkc/logstash-syslog

Logstashのデプロイ

AWXをデプロイしてるK8sクラスターにHemlを使ってデプロイします。

Logstash単体のHelmチャートのリポジトリはここにあるけど、現在メンテナンスされておらず、ECK(Elastic on Kubernetes)を使うのが推奨されています。
ECKのHelmチャートのリポジトリの追加はこの辺りを参照。

helm repo add elastic https://helm.elastic.co
helm repo update

あとはリポジトリelastic/logstashがあるので、これをデプロイします。(values.yamlファイルについては後述)

[zaki@cloud-dev2 logstash]$ helm upgrade --install sample-logstash elastic/logstash -f values.yaml --create-namespace -n logging
Release "sample-logstash" does not exist. Installing it now.
NAME: sample-logstash
LAST DEPLOYED: Sun Jan 21 14:41:22 2024
NAMESPACE: logging
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Watch all cluster members come up.
  $ kubectl get pods --namespace=logging -l app=sample-logstash-logstash -w
[zaki@cloud-dev2 logstash]$ helm ls -n logging
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
sample-logstash logging         1               2024-01-21 14:41:22.146914228 +0900 JST deployed        logstash-8.5.1  8.5.1      
[zaki@cloud-dev2 logstash]$ 
[zaki@cloud-dev2 logstash]$ 
[zaki@cloud-dev2 logstash]$ kubectl get pod -n logging -w
NAME                         READY   STATUS    RESTARTS   AGE
sample-logstash-logstash-0   0/1     Running   0          20s
sample-logstash-logstash-0   1/1     Running   0          80s

readyになるまでに若干時間がかかります。

values.yamlの説明

デプロイに使ったvalues.yamlファイルは以下の通り。

---
logstashConfig:
  logstash.yml: |
    http.host: "0.0.0.0"

logstashPipeline:
  logstash.conf: |
    input {
      http {
        port => 9601
      }
    }
    output {
      syslog {
        host => "192.168.0.16"
        port => 514
      }
    }

image: "zakihmkc/logstash-syslog"
imageTag: "8.5.1"

service:
  type: ClusterIP
  ports:
    - name: http
      port: 9601
      protocol: TCP
      targetPort: 9601

logstashConfig

/usr/share/logstash/config 以下のファイル(settings)を定義(override)します。
Docker版はlogstash.ymlファイルがデフォルトで以下の内容になっており、そのままだとoutputプラグインにsyslogを使用してもhttp://elasticsearch:9200へのリクエストが発生して名前解決エラーなどが延々記録されるので上書き削除します。

$ cat config/logstash.yml 
http.host: "0.0.0.0"
xpack.monitoring.elasticsearch.hosts: [ "http://elasticsearch:9200" ]

ただしコメントにもある通りhttp.host: 0.0.0.0が無いとpodのliveness probeで条件(/へのHTTP GET)を満たさないのでこれだけ定義します。

logstashPipeline

/usr/share/logstash/pipeline以下のパイプライン設定(conf)を定義(override)します。
AWXからのアクセスはHTTPで受信し、リモートのsyslogへ送信するため、http input pluginsyslog output pluginを指定します。

Http input plugin

Logstash本体が9600でListenするので、適当だけどプラグインとしては9601でListenするように設定。

Syslog output plugin

最低限必須のパラメタとして、syslogサーバーのIPアドレスとポート番号を指定。
ここでは192.168.0.16で動作してる外部のLinuxサーバーのsyslogをUDP受付するようにしてるのでそれを指定。

ちょっと古いけど参考 zaki-hmkc.hatenablog.com

imageとimageTag

前述のとおり、Syslogプラグイン入りのカスタムイメージを指定

service

デフォルトではLogstash本体用のServiceリソースしか作成されないので、http input plugin用のService定義を追加。
外部アクセスは不要でクラスター内のAWXからアクセスできればOKなので、ClusterIPで定義。

AWXロギング設定

左側設定メニューの「ロギング設定」(englishだと"Logging settings")で以下の内容を設定します。

項目 項目(ja)
Enable External Logging 外部ログの有効化 On
Logging Aggregator ログアグリゲーター (1)
Logging Aggregator Port ログアグリゲーターポート (2)
Logging Aggregator Type ログアグリゲーターのタイプ logstash
Logging Aggregator Protocol ログアグリゲータープロトコル HTTPS/HTTP
  • (1) ログアグリゲーターにLogstashのURLを入力。http://<service名>.<namespace名>.svc.cluster.local
    • アドレスのみを入力するとHTTPS接続になるため、今回の構成のようにHTTPの場合は http:// から入力
    • 本記事のデプロイ手順であれば、namespaceがlogging、リリース名はsample-logstashなので、URLはhttp://sample-logstash-logstash.logging.svc.cluster.local
  • (2) ポート番号はhttp input plugin用のserviceがListenするポート番号
    • 本記事のvalues.yaml定義の内容であれば9601 (podのlistenする番号とserviceのポートを同じにしている)

これでAWXでジョブ実行した際のログがまずLogstashへ転送され、そこからさらにsyslogサーバーへ転送されます。

AAPの場合

上の構成のLogstashをLoadBalancer Serviceで外部公開して、同様の設定をいれればログを転送できます。
(Ansible Automation Platform Controller 4.4.8で確認)

要確認ポイント

  • デフォルト設定の「ログアグリゲーターレベルのしきい値」を「INFO」にするとタスク単位でログが記録されるので膨大になりがち
    • 閾値を「WARN」にするとエラー時のログから記録されるが、タスクのエラーで発生するエラーメッセージは記録されないぽいので、結局は大元のログの確認が必要
  • outputプラグインでsyslogを使用しても、stdoutへは受信したログを出力されるらしくpodのログとしても記録される
    • fluentbitを使ったpodログ収集を行うと二重にロギングしてしまうので、除外設定をするか、Logstashではsyslog転送せずにfluentbitに転送処理を寄せるのもアリかも

参考リンク (1/27追加)


アグリー! (これが言いたかっただけ)

[Ansible / AAP / AWX] EEのコンテナイメージ作成にAnsible Builderは必ずしも必要ないかを確認してみた (小ネタ)

AAPやAWXでジョブ実行に必要なExecution Environments(EE)を作るために通常はAnsible Builderを使ってコンテナイメージをビルドするが、要件を満たせばDockerfileを直接書いて自前でビルドしても動くのでは…と思ったので確認してみた。

結論としては、以下が入っていれば最低限動作した。

  • Ansible Core
    • プラス必要なAnsible Collection
  • Ansible Runner
  • SSHクライアント
    • sshpass (パスワード認証の場合)

Ansible BuilderではRHEL系コンテナしか使えない(夏にv3対応したときにいろいろ試したがRHEL系以外はダメだった)が、お試しでDebian系…もいいかと思ったけど、せっかくなのでAnsible利用時にあまり使われず(個人の感想)、イメージサイズがより小さいAlpine Linuxでビルドして確認。

Dockerfile

2024.01.08時点で公開されてるAlpine 3.19.0のイメージをベースにするなら以下の内容でビルドすればOK
(実行確認用の追加パッケージ有)

FROM alpine:3.19.0

RUN apk add ansible-core py3-jmespath py3-pip openssh-client sshpass && \
    ansible-galaxy collection install community.general && \
    pip install ansible-runner --break-system-packages

Alpineのパッケージでansible-coreをインストールすると、Alpine 3.19の場合はcore 2.16.1がインストールされるのでこれを利用。
(ansibleパッケージもあり、こちらを使えばオフィシャルのコレクションは多分すべて入る。pip install ansibleと同等と思われる)

Ansbile Runnerは必須だがAlpineのパッケージでは提供されないため、pipを使用する。その際、venvを使わずシステムに直接インストールしようとするとパッケージ管理の競合対策のためエラーになるが、コンテナを使った限定された環境なので--break-system-packagesオプションを付加する。

SSHクライアントも(自動化で標準的に使用するSSHコネクションプラグインを使用するのであれば)必要。Alpineのコンテナにはデフォルトではsshが無いため、追加インストールしないと下記エラーが発生する。

The command was not found or was not executable: ssh-agent.

また、SSH鍵認証でなく、パスワード認証を使用する場合はsshpassも必要。無いと下記エラーになる。

"to use the 'ssh' connection type with passwords or pkcs11_provider, you must install the sshpass program"

あとは実行例としてjson_queryを試すために、APKパッケージ版jmespathcommunity.generalコレクションを追加している。
使ったplaybookは基本的にこんな感じ
localhost実行のため、これとあとリモートに接続に行く適当なジョブを実行

これでビルドしたイメージをAAP/AWXでそれぞれ実行環境として試したが、ジョブの実行は問題なかった。

イメージサイズ比較

※ これはビルドしただけで動作は未確認

Ansible Builderを使ってrockylinux:9.3-minimalベースに前述Dockerfile同様jmespathとcommunity.generalを入れたイメージサイズと比較。
また、Ansible Coreもpipで入れた場合とも比較。これも若干増えた。

  • apk add版: 127MB
  • pip版: 148MB
  • Ansible Builder版(rockylinux:9.3-minimalベース): 280MB

前述のパッケージ版をpip版に置き換えたこの場合のDockerfileは以下の通り。

FROM alpine:3.19.0

RUN apk add py3-pip openssh-client sshpass && \
    pip install ansible-runner ansible-core==2.16.1 jmespath --break-system-packages && \
    ansible-galaxy collection install community.general

Ansible Builder用execution-environment.ymlファイルは以下の通り。

---
version: 3

dependencies:
  ansible_core:
    package_pip: ansible-core==2.16.1
  ansible_runner:
    package_pip: ansible-runner
  galaxy:
    collections:
      - community.general
  python:
    - jmespath
  system:
    - sshpass
  python_interpreter:
    python_path: /usr/bin/python3.11

images:
  base_image:
    name: docker.io/library/rockylinux:9.3-minimal

options:
  package_manager_path: /usr/bin/microdnf  # required (when use rocky/alma)

additional_build_steps:
  prepend_base:
    - RUN microdnf install python3.11 -y   # for alma/rocky minimal

詳しくは下記参照

zaki-hmkc.hatenablog.com

--break-system-packagesオプション

何をOSのパッケージ管理(apk)で入れて何をPythonのパッケージ管理(pip)で入れるかは、あまりごちゃごちゃさせると文字通り競合するかもしれないので、どちらかに寄せるのが良いと思う。
この辺りは深く検証してないので都度対応かな。

で、運用とかで使える?

自己責任で(ry

公式ドキュメントで「Ansible Builderを使わない場合はこんな感じで~」的な記載を見つけられず表技か裏技かは微妙なことをしてるため、あくまで「動かしたいplaybookがあってそれが動く専用のEEのイメージを用意する」のであれば手段の一つとしてありかもしれない。特に「求めているコンテナイメージの完成形はわかっているのに、Ansible Builder(というかexecution-environment.yml)でどう実装すればわからない場合とか。
あとはAnsible Builderを使うとイメージサイズが大きすぎて困る…みたいなときは良いかもね。

今回はsshとjmespathを追加インストールしてるけど、EEに必要なパッケージ類は実行するplaybookで使用しているモジュール・プラグイン次第なので、必要に応じて追加する。(不足していれば実行時にエラーになるので、メッセージを頼りに不足分を補う地道な作業になるはず。ってこれはAnsible Builderを使っていても変わらないと思う)

あと一つ発見だったのは、Alpineのパッケージ管理はAnsibleインストールできるしバージョンも最新にかなり近いものを追従してるという点。


根本的におかしいとか、こうすればもっといいとか、ここに公式情報あるとかあれば、ぜひ教えてください。

[Ansible / AAP / AWX] AnsibleでAAPリソースの作成を自動化する

本エントリは「エーピーコミュニケーションズ Advent Calendar 2023」の24日目のエントリです。クリスマスイブ🎄は一切関係ないネタです。

Ansible Automation PlatformのAutomation Controller(旧Ansible Tower / 以下AAP)AWXGUIでAnsibleを実行することができるアプリケーションですが、率直に言ってマウス使って操作…とくにプロジェクトやテンプレート作ったりするのって億劫じゃないでしょうか?私は嫌ですやりたくありません(直球)
この記事ではそんなGUI操作が必要なAAPやAWXの操作をAnsibleで自動化するためのタスクの実装について紹介します。

接続方法

サーバーアドレス

最初に戸惑いがちなところですが、抑えるべきポイントは「タスクが実行されるホスト(hosts指定のホスト)から見たAAPのアドレスを指定する」です。
指定の方法は2通りあり、環境変数CONTROLLER_HOSTで指定するか、タスクごとに各モジュールのcontroller_hostパラメタで指定です。
基本的には1台のAAPに対して様々なリソースを作成していくパターンが多いので、その場合は環境変数をプレイに指定すれば各タスクの記述が減ってplaybookがスッキリします。本記事では環境変数で指定している前提でタスク実装の紹介していきます。

認証情報

これもサーバーアドレスと同様に、環境変数もしくは、タスクごとに各モジュールのcontroller_usernamecontroller_passwordで指定します。パスワード認証でなくトークンを使う場合は環境変数CONTROLLER_OAUTH_TOKENかパラメタcontroller_oauthtokenを使います。
通常はサーバーアドレスとセットで指定することになると思います。

運用時の操作であれば適切なロールを設定したユーザーのトークンを使うのが良いですが、AAP/AWXインストールから初期構築までをすべて自動化するのであればadminとそのパスワード認証を指定することになります。
(ゼロの状態からトークンでの認証ってできない…よね?)

実装例

ローカル実行でリモートのAAPへ接続する場合

おそらく一番よくあるパターン。
localコネクションプラグインでタスクはローカル実行しつつ、CONTROLLER_HOSTでAAPサーバーを指定する構成。

---
- hosts: localhost
  environment:
    CONTROLLER_HOST: 192.168.0.79
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

また、AAPの場合はHTTPS接続でポートも443/TCPなのでアドレスのみ指定すればOKですが、AXWは構成によっては(Kubernetesからの公開方法によっては)HTTPだったりポート番号もデフォルト以外の場合もあります。
その場合は、httpから記述すればOK
(この場合はCONTROLLER_VERIFY_SSLは無くても動く)

- hosts: localhost
  environment:
    CONTROLLER_HOST: http://192.168.0.75:8080
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: ...

タスク実行ノードを中継サーバーにする場合

Ansibleを実行するノードからAAPへ疎通がない場合などの構成。
hostsで指定する対象ノードから見たAAPのアドレスをCONTROLLER_HOSTに指定。

---
- hosts: server
  environment:
    CONTROLLER_HOST: 172.29.1.21
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

AnsibleのマネージドノードとしてAAPのホストに直接接続

aapに指定しているサーバーにSSH接続し、そこからlocalhostで接続する、という構成。
複数のAAPサーバーがあり、設定内容が同一であれば、この構成ならまとめて処理できます。

- hosts: aap
  environment:
    CONTROLLER_HOST: localhost
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: curry_tabetai

  tasks:
    # ...

AWXの場合はkubectlコネクションプラグイン使用

SSH接続する構成が取れるのはAAPの場合で、AWXの場合はコンテナ内でSSHは動いていないため、同様の構成をとる場合はkubectlコネクションプラグインを使う必要があります。

- hosts: awx
  environment:
    CONTROLLER_HOST: http://localhost:8052
    CONTROLLER_USERNAME: admin
    CONTROLLER_PASSWORD: ...

※ 以下は2023年12月時点での内容。AWXはたまにコンテナ構成が変わるので、その場合は指定する対象が変化するかも。おそらく「web」に対して接続すればよいと思うけど…

HTTPであることと、接続先のポートが8052なのがポイント(これはServiceリソースのtargetPortで確認できます)

インベントリファイルは以下のようになります。
この例はAWXはawxネームスペースにawx-demoの名前でデプロイされています。対象はWebのPodでここではawx-demo-web-68b54f6bdf-mx55sというPod名です。 (いくつかのコンテナで構成されていますがwebのコンテナに接続します)

[awx]
awx ansible_kubectl_pod=awx-demo-web-68b54f6bdf-mx55s ansible_kubectl_namespace=awx ansible_kubectl_container=awx-demo-web

[all:vars]
ansible_connection=kubectl

kubectlを実行したときのpodとserviceの状態は以下の通り。

$ kubectl get pod,svc -n awx
NAME                                                   READY   STATUS    RESTARTS   AGE
pod/awx-operator-controller-manager-76b545976d-jlcts   2/2     Running   0          16h
pod/awx-demo-postgres-13-0                             1/1     Running   0          16h
pod/awx-demo-task-5ff94fb4d9-65zdt                     4/4     Running   0          16h
pod/awx-demo-web-68b54f6bdf-mx55s                      3/3     Running   0          15h

NAME                                                      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)          AGE
service/awx-operator-controller-manager-metrics-service   ClusterIP      10.43.167.210   <none>         8443/TCP         16h
service/awx-demo-postgres-13                              ClusterIP      None            <none>         5432/TCP         16h
service/awx-demo-service                                  LoadBalancer   10.43.194.190   192.168.0.75   8080:30448/TCP   16h

kubectlコネクションプラグインについては以下参照。

zaki-hmkc.hatenablog.com

パスワードでなくトークンを使用する場合

ここまではCONTROLLER_PASSWORDを使ってパスワードを指定した例でしたが、トークン(パーソナルアクセストークン)の場合は以下の通り。

  environment:
    CONTROLLER_HOST: 192.168.0.79
    CONTROLLER_VERIFY_SSL: false
    CONTROLLER_USERNAME: zaki
    CONTROLLER_OAUTH_TOKEN: ....... (ユーザーメニューの「トークン」で作成するトークン文字列)

リソース作成

サブスクリプション

awx.awx.licenseモジュールを使用します。
これはAWXになくAAPのみ。
実行ノード上にzipのマニフェストファイルを配置しておき、パスを指定します。

  - name: set the license
    awx.awx.license:
      manifest: manifest.zip

マニフェストファイルの作成方法は以下参照。

zaki-hmkc.hatenablog.com

認証情報

awx.awx.credentialモジュールを使用します。
よく使うものを紹介。

ソースコントロール

Gitリポジトリにアクセスするための認証情報で、主にプロジェクトの更新時に使用。

- name: create git repository credential
  awx.awx.credential:
    name: git-repository-credential
    organization: Default
    credential_type: Source Control
    inputs:
      username: username
      password: password

公開鍵認証で秘密鍵を登録したい場合は、passwordでなくssh_key_dataを使用して、鍵ファイルの内容を指定します。

    inputs:
      username: zaki
      ssh_key_data: '{{ lookup("file", "~/.ssh/id_rsa") }}'

マシン

Ansible実行時のホストに対するSSH認証情報として使用。

- name: create ssh credential
  awx.awx.credential:
    name: server
    organization: Default
    credential_type: Machine
    inputs:
      username: username
      password: password

SSH秘密鍵を登録するのであれば前述ソースコントロールの場合と同様にssh_key_dataを使用します。

コンテナレジストリ

認証が必要なコンテナレジストリで使用。
例えばDocker Hubへのログインであればdocker.ioを指定します。

- name: create registry credential
  awx.awx.credential:
    name: dockerhub credential
    organization: Default
    credential_type: Container Registry
    inputs:
      host: docker.io
      username: username
      password: password

カスタム認証情報タイプ

認証情報タイプの作成

プリセットの認証情報の型で足りない場合にカスタム認証情報タイプを作成する場合、awx.awx.credential_typeモジュールを使用します。 以下は「ホスト名」「ユーザー名」「パスワード」の3値を一つの情報として定義する例。

インジェクター{{ 変数名 }}という文字列を設定するため、rawを使ってJinja2のテンプレート処理を無効にしているのがポイント。

  - name: create credential type for host
    awx.awx.credential_type:
      name: host-auth-info
      kind: cloud
      inputs:
        fields:
          - id: username
            type: string
            label: username
          - id: password
            type: string
            label: password
            secret: true
          - id: hostname
            type: string
            label: hostname
      injectors:
        extra_vars:
          auth_username: "{% raw %}{{ username }}{% endraw %}"  # "{{ uaesrname }}"
          auth_password: "{% raw %}{{ password }}{% endraw %}"  # "{{ password }}"
          auth_hostname: "{% raw %}{{ hostname }}{% endraw %}"  # "{{ hostname }}"

ちなみにkindは必須項目でドキュメントを見るとcloudnetが指定できると書かれているが、どこに作用しているかは不明。。あまり影響しなさそうな気がしますが。。

Note that only cloud and net can be used for creating credential types.

上記の自作認証情報タイプの認証情報

前述host-auth-infoの認証情報タイプを使った認証情報を作成するには、credential_typeに認証情報タイプ名を指定します。

  - name: create my credential
    awx.awx.credential:
      name: my-credential
      organization: Default
      credential_type: host-auth-info
      inputs:
        hostname: nijigasaki.example.org
        username: zaki
        password: tokimeki

ジョブ関連

インベントリとホスト

awx.awx.inventoryモジュールを使用します。
ホスト作成時にインベントリの指定があるため、まずインベントリを作成してからホストを作成します。

インベントリ作成

- name: create inventory
  awx.awx.inventory:
    name: my inventory
    organization: Default

ホスト作成

awx.awx.hostモジュールを使用します。
inventoryに作成済みインベントリを指定して、そこに属するホストを登録します。
ホスト変数の指定がある場合はvariablesで指定します。

- name: add host to inventory
  awx.awx.host:
    name: "{{ item.name }}"
    inventory: my inventory
    variables:
      ansible_host: "{{ item.addr }}"
  loop:
    - name: server1
      addr: 192.168.0.71
    - name: server2
      addr: 192.168.0.72

プロジェクト作成

awx.awx.projectモジュールを使用します。
認証が不要なGitリポジトリとしてhttps://github.com/zaki-lknr/ansible-sample.gitを指定するなら以下。

  - name: create project
    awx.awx.project:
      name: sample project
      organization: Default
      scm_type: git
      scm_url: https://github.com/zaki-lknr/ansible-sample.git
      scm_update_on_launch: false

認証が必要なリポジトリの場合はcredentialパラメタに作成済みの認証情報名を指定します。

      credential: git-repository-credential

実行環境

awx.awx.execution_environmentモジュールを使用してコンテナレジストリ上のEEコンテナを指定します。
認証が必要な場合は、credentialに「credential_type=Container Registry」を指定して作ったクレデンシャルリソースを指定します。
pullにはイメージpullポリシーを指定します。

- name: create ee setting
  awx.awx.execution_environment:
    name: Network Automation Execution Environment
    image: docker.io/zakihmkc/ansible-ee:1.2.3
    pull: missing
    credential: dockerhub credential

ジョブテンプレート

ジョブテンプレートを作成するには、awx.awx.job_templateモジュールを使用してここまで掲載したプロジェクトやインベントリ、認証情報、EEなどを指定します。
playbookにはリポジトリに存在するプレイブックのファイル名を指定する必要があります。(存在しないとエラー)

- name: create job template
  awx.awx.job_template:
    name: sample job template
    job_type: run
    project: sample project
    organization: Default
    inventory: my inventory
    credentials:
    - server1
    - my-credential
    playbook: path/to/playbook.yml
    verbosity: 0
    execution_environment: my-image

ワークフローテンプレート…は長いのでまた別途 m__m

awx.awx.workflow_job_templateを使って定義します。
schema以下に実行したジョブテンプレートを記述して、relatedに成功時・失敗時などの次のジョブを指定します。それぞれのジョブテンプレートはidentifierでタスク定義内で使用するIDを割り振ってそのIDを指定します。

スケジュール

作成済みジョブテンプレートを定期実行させたい場合はawx.awx.scheduleモジュールを使って作成します。
スケジュール定義はrruleに記述しますが、生で書くのは若干複雑というほどでもないけどハードコーディングせざるを得なくなるため、awx.awx.schedule_rruleset lookupプラグインを使用すると管理しやすいです。
ただしschedule_rrulesetを使用するにはPythonpytzパッケージが必要なのでpipでインストールしておく必要があります。

- name: create schedule
  awx.awx.schedule:
    name: sample job template schedule
    unified_job_template: sample job template
    rrule: "{{ query('awx.awx.schedule_rruleset', start_time, rules=rrules, timezone='Asia/Tokyo') }}"
  vars:
    rrules:
      - frequency: 'day'
        interval: 1
    # 現在日(リソース作成日)の12時を基準に"%Y-%m-%d HH:MM:SS"書式の文字列作成
    start_time: "{{ now(false, '%Y-%m-%d') }} 12:00:00"

あとschedule_rruleもあって、どちらを使っても以下の警告が出て解決できなかった(運用では実際はこっち使ってたりするけど)
メッセージの感じからplaybookの書きっぷりじゃなくてプラグインの内部実装の方に修正が必要そうな感じがしなくもないけど…詳細不明。

[DEPRECATION WARNING]: The lookup plugin 'awx.awx.schedule_rruleset' was expected to return a list,
got '<class 'str'>' instead. The lookup plugin 'awx.awx.schedule_rruleset' needs to be changed to return a list. 
This will be an error in Ansible 2.18. This feature will be removed in version 2.18.
Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

…なんかここまで書いてるとlookup plugin使わず生で書いてもよいかもしれない。。

その他

設定

設定についてはawx.awx.settingsモジュールを使用します。
インベントリファイルへの指定でできそうだけどわからなかった「ベースURLの指定」をセットするには以下の通り。
自動化を実行するだけであればこの設定は特にイジらなくても大丈夫だけど、ベースURLは「通知」を行う場合のメッセージ内のURLに使われるので、使い方によってはきちんと設定しておきたい値。

- name: update base url
  awx.awx.settings:
    name: TOWER_URL_BASE
    value: "https://ansible.example.org"

他には、例えばAWX_ISOLATION_SHOW_PATHSに空リストを設定するには以下。

- name: unset isolation path (workaround)
  awx.awx.settings:
    name: AWX_ISOLATION_SHOW_PATHS
    value: []

access.redhat.com

ジョブの起動

ジョブを起動するにはawx.awx.job_launchモジュールを使用します。
job_templateにジョブテンプレート名を指定すればOK

- name: launch sample job template
  awx.awx.job_launch:
    job_template: sample job template

通知のテスト

モジュールはなさそうなので、uriを使ってAPIを直接叩きます。
URLに含める必要がある通知ID(以下例でのnotify_id)は事前に知っておく必要があります。

- name: notification test
  # モジュールが無さそうなのでRESTで実行
  ansible.builtin.uri:
    method: POST
    url: "https://ansible.example.org/api/v2/notification_templates/{{ notify_id }}/test/"
    url_username: admin
    url_password: "{{ password }}"
    validate_certs: false
    force_basic_auth: true
    status_code: [200, 201, 202]  # ドキュメントは201となってるが実際は202が返る

ほんとは先に「AAP/AWXの結果をSlackに通知する設定」という記事を書いておきたかったのに…いつか書きます。

docs.ansible.com


環境

  • Ansible実行元
  • 自動化対象
    • AAP Controller 4.4.8 (on RHEL9 / AAP 2.4.3)
    • AWX 23.4.0 (on K3s 1.28.4)

弊社では各展示会に出展する際に「ネットワーク自動化デモ」を実施しています。

automation.ap-com.co.jp

本記事で紹介したタスクはこのデモ環境を構築するときに使用しており、環境の構築をAnsibleで自動化しています。

(以下懺悔) 1年以上前からこの構築を行っててAnsible使った自動構築の情報としては持っていたので「いつかブログでまとめよう…」と思いつつもずっと保留してる間に、チームメンバーが何人も「AnsibleでAAPリソース作成の自動化」を行う機会があって、でもわかりやすい情報共有ができずに「デモの自動構築のソース参考になるよ」とリポジトリの場所を教えることしかできなかったという機会損失が今年は何度もあり、本当に申し訳ありませんでした。(これが言いたかった)