hana_shinのLinux技術ブログ

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

プライバシーポリシー

個人情報について
利用目的
当ブログ「hana_shinのLinux技術ブログ」では、お問い合わせの際に、お名前、メールアドレス等の個人情報をご登録いただいています。

これらの個人情報は、質問に対する回答や必要な情報をご連絡する場合に利用させていただくものです。個人情報をこの目的以外で利用することはございません。

また当ブログでは、スパム・荒らしへの対応として、コメントの際に使用された IPアドレスを記録しています。

これは、はてなブログの標準機能としてサポートされているものです。スパム・荒らしへの対応以外にこの IPアドレスを使用することはありません。


個人情報の保管
ユーザーの個人情報を正確かつ最新の状態に保ち、個人情報への不正アクセス・紛失・破損・改ざん・漏洩などを防止するため、セキュリティシステムの維持など必要な措置を講じて、個人情報の厳重な管理を行ないます。

個人情報の開示
【第三者への開示】

次の場合を除いて、ユーザーからいただいた個人情報を、第三者に開示することはありません。

提供者の同意がある場合
法令に基づく場合
不正行為やその他の違法行為を防ぐために個人情報の開示が必要となった場合
【本人への開示】

ご本人の個人情報の照会・修正・削除などをご希望される場合には、ご本人であることを確認の上、対応させていただきます。

広告の配信について
Google アドセンスなど
「hana_shinのLinux技術ブログ」は第三者配信の広告サービス「Google アドセンス」「A8.net」「もしもアフィリエイト」を利用しています。
広告配信事業者は、ユーザーの興味に応じた広告を表示するためにCookie(クッキー)を使用します。Cookieを使用することでユーザーのPCを識別できるようになりますが、ユーザー個人を特定できるものではありません。

Cookieを無効にするやGoogleアドセンスに関する詳細は「広告 – ポリシーと規約 – Google」をご覧ください。

Amazon アソシエイト
「hana_shinのLinux技術ブログ」は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイトプログラムである、Amazonアソシエイト・プログラムの参加者です。 Amazonのアソシエイトとして、「hana_shinのLinux技術ブログ」は適格販売により収入を得ています。

Amazonアソシエイトの個人情報の取扱方法についてはAmazonプライバシー規約をご覧ください。

アクセス解析ツール
「hana_shinのLinux技術ブログ」では、Googleが提供している分析ツールGoogle Analyticsを利用して、訪問者の行動を分析しています。Google Analytics のデータのプライバシーとセキュリティについてはコチラをご覧ください。

Google Analyticsはトラフィックデータの収集のためにCookieを使用しています。このトラフィックデータは匿名で収集されており、個人を特定するものではありません。

この機能はCookieを無効にすることで収集を拒否することが出来ますので、お使いのブラウザの設定をご確認ください。

免責事項
当ブログからリンクやバナーなどによって他のサイトに移動された場合、移動先サイトで提供される情報、サービス等について一切の責任を負いません。

当サイトのコンテンツ・情報につきまして、可能な限り正確な情報を掲載するよう努めておりますが、誤情報が入り込んだり、情報が古くなっていることもございます。

当サイトに掲載された内容によって生じた損害等の一切の責任を負いかねますのでご了承ください。

また、当サイトに掲載しているすべての記事は、予告なしに変更・削除されることがあります。 予めご了承下さい。

肖像権について
当ブログは著作権や肖像権の侵害を目的としたものではありません。著作権や肖像権に関して問題がございましたら、お問い合わせフォームよりご連絡ください。確認後、速やかに対応いたします。

著作権について
「hana_shinのLinux技術ブログ」に掲載されている情報についての著作権は放棄しておりません。掲載している文章や画像などにつきましては、無断転載することを禁止します。

当ブログからの引用に関しましては「引用元の明示」によって無償で引用頂けます。

ただし、全文転載はお断りいたしております。引用許可範囲についても、事前予告なくこれを変更する事があります。

プライバシーポリシーの変更について
「キラッとブログ」は、個人情報に関して適用される日本の法令を遵守するとともに、本ポリシーの内容を適宜見直しその改善に努めます。

修正された最新のプライバシーポリシーは常に本ページにて開示されます。


運営者:hana-shin

初出掲載:2019年05月05日

Kubernetesのサイドカーを試す~TLS終端による通信の暗号化

1 はじめに

本記事では、HTTPで動作するメインコンテナと、TLS終端を行うEnvoyサイドカーを同じPod内で動作させます。クライアントからのHTTPS通信をEnvoyが受け取り、TLS終端後のHTTP通信をメインコンテナへ転送することで、メインコンテナにTLS設定を持たせることなく、クライアントとEnvoy間の通信を暗号化します。

この記事を通して、以下の内容を理解することを目標とします。

  • サイドカーとは何か、およびKubernetesで利用される目的
  • 1つのPod内でメインコンテナとTLSサイドカーを同時に動作させる方法
  • TLSサイドカーがサーバ証明書と秘密鍵を使用してHTTPS通信を受け付ける仕組み
  • サイドカーがHTTPS通信を復号し、HTTP通信としてメインコンテナへ転送する仕組み
  • curlコマンドを使用して、サイドカー経由のHTTPS通信が正常に動作していることを確認する方法
クライアント
    |
    | HTTPS :443
    v
Service
    |
    | targetPort: 8443
    v
Pod
|
+-- Envoyサイドカー
|      listen :8443
|      TLS終端
|         |
|         | HTTP
|         | 127.0.0.1:8080
|         v
|
+-- nginxメインコンテナ
       listen :8080

このように、Envoyサイドカーがサーバ証明書と秘密鍵を使用してTLS終端を行うため、メインコンテナはHTTPのまま動作させることができます。

なお、実際の運用では、証明書や秘密鍵をKubernetesのSecretとして管理し、cert-managerなどを使用して証明書の発行や更新を自動化する構成があります。また、Istioなどのサービスメッシュを利用すると、通信を中継するプロキシを介して、ワークロード間の通信をmTLSで暗号化し、通信相手を相互に認証することもできます。

本記事では、TLS終端を行うサイドカーの基本的な仕組みを理解することに焦点を当て、検証用の証明書を使用して動作を確認します。

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@contol ~]# kubectl version
Client Version: v1.36.3
Kustomize Version: v5.8.1
Server Version: v1.36.3

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

EnvoyでTLS終端を行うためには、HTTPS通信で使用するサーバ証明書と秘密鍵が必要です。本記事では、検証用のCA(認証局)を作成し、そのCAを使用してEnvoy用のサーバ証明書を発行します。作成したサーバ証明書と秘密鍵は、後ほどKubernetesのTLS Secretに格納し、Envoyコンテナから参照できるようにします。

この章では、次のファイルを作成します。

ファイル 用途
ca.key 検証用CAの秘密鍵
ca.crt 検証用CAの証明書
envoy.key EnvoyがTLS通信で使用するサーバ秘密鍵
envoy.csr Envoy用サーバ証明書を発行するためのCSR(証明書署名要求)
envoy.ext サーバ証明書にSANなどの拡張情報を付与するための設定ファイル
envoy.crt EnvoyがTLS通信で使用するサーバ証明書

3.1 検証用CAの作成

(1) 作業ディレクトリの作成
証明書関連のファイルをまとめて管理するため、作業用ディレクトリを作成します。

[root@control ~]# mkdir -p ~/envoy-tls
[root@control ~]# cd envoy-tls/
[root@control envoy-tls]#

(2) CA秘密鍵の作成
envoyのサーバ証明書を署名するためのCA秘密鍵を作成します。

[root@control envoy-tls]# openssl genpkey \
  -algorithm RSA \
  -pkeyopt rsa_keygen_bits:4096 \
  -out ca.key

CA秘密鍵(ca.key)が作成されたことを確認します。

[root@control envoy-tls]# ls -l
合計 4
-rw-------. 1 root root 3272  8月  7 13:54 ca.key

(3) CA証明書の作成
作成したCA秘密鍵を使用して、自己署名のCA証明書を作成します。

[root@control envoy-tls]# openssl req \
  -x509 \
  -new \
  -key ca.key \
  -sha256 \
  -days 3650 \
  -out ca.crt \
  -subj "/C=JP/ST=Tokyo/L=Tokyo/O=Example/OU=Example/CN=Envoy Test CA"

CA証明書(ca.crt)が作成されたことを確認します。

[root@control envoy-tls]# ls -l
合計 8
-rw-r--r--. 1 root root 2037  8月  7 13:56 ca.crt
-rw-------. 1 root root 3272  8月  7 13:54 ca.key

3.2 Envoy用サーバ秘密鍵の作成

次に、EnvoyがTLS通信で使用するサーバ秘密鍵を作成します。

[root@control envoy-tls]# openssl genpkey \
  -algorithm RSA \
  -pkeyopt rsa_keygen_bits:2048 \
  -out envoy.key

envoyの秘密鍵(envoy.key)が作成されたことを確認します。

[root@control envoy-tls]# ls -l
合計 12
-rw-r--r--. 1 root root 2037  8月  7 13:56 ca.crt
-rw-------. 1 root root 3272  8月  7 13:54 ca.key
-rw-------. 1 root root 1708  8月  7 13:59 envoy.key

3.3 CSRの作成

作成したenvoyの秘密鍵を使用して、CSR(証明書署名要求)を作成します。

[root@control envoy-tls]# openssl req \
  -new \
  -key envoy.key \
  -out envoy.csr \
  -subj "/C=JP/ST=Tokyo/L=Tokyo/O=Example/OU=Example/CN=envoy.home.lab"

CSR(envoy.csr)が作成されたことを確認します。

[root@control envoy-tls]# ls -l
合計 16
-rw-r--r--. 1 root root 2037  8月  7 13:56 ca.crt
-rw-------. 1 root root 3272  8月  7 13:54 ca.key
-rw-r--r--. 1 root root 1009  8月  7 14:02 envoy.csr
-rw-------. 1 root root 1708  8月  7 13:59 envoy.key

3.4 Envoyサーバ証明書の作成

3.4.1 Envoyサーバ証明書の拡張情報の作成

サーバ証明書にSAN(Subject Alternative Name)やサーバ認証用途などの情報を付与するため、拡張情報ファイルを作成します。HTTPSクライアントは、接続先のホスト名と証明書のSANが一致するかを確認します。そのため、後ほどenvoy.home.labという名前でHTTPSアクセスします。

[root@control envoy-tls]# vi envoy.ext
[root@control envoy-tls]# cat envoy.ext
authorityKeyIdentifier = keyid,issuer
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = envoy.home.lab
3.4.2 Envoyサーバ証明書の作成

CSRを検証用CAで署名し、Envoyのサーバ証明書を作成します。

[root@control envoy-tls]# openssl x509 \
  -req \
  -in envoy.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out envoy.crt \
  -days 365 \
  -sha256 \
  -extfile envoy.ext
Certificate request self-signature ok
subject=C=JP, ST=Tokyo, L=Tokyo, O=Example, OU=Example, CN=envoy.home.lab

Envoyのサーバ証明書(envoy.crt)が作成されたことを確認します。

[root@control envoy-tls]# ls -l
合計 28
-rw-r--r--. 1 root root 2037  8月  7 13:56 ca.crt
-rw-------. 1 root root 3272  8月  7 13:54 ca.key
-rw-r--r--. 1 root root   41  8月  7 14:10 ca.srl
-rw-r--r--. 1 root root 1773  8月  7 14:10 envoy.crt
-rw-r--r--. 1 root root 1009  8月  7 14:02 envoy.csr
-rw-r--r--. 1 root root  215  8月  7 14:07 envoy.ext
-rw-------. 1 root root 1708  8月  7 13:59 envoy.key

3.5 サーバ証明書の確認

[root@control envoy-tls]# openssl x509 \
  -in envoy.crt \
  -noout \
  -subject \
  -issuer \
  -dates
subject=C=JP, ST=Tokyo, L=Tokyo, O=Example, OU=Example, CN=envoy.home.lab
issuer=C=JP, ST=Tokyo, L=Tokyo, O=Example, OU=Example, CN=Envoy Test CA
notBefore=Aug  7 05:10:37 2026 GMT
notAfter=Aug  7 05:10:37 2027 GMT

SAN(Subject Alternative Name)に、HTTPS通信で使用するホスト名が登録されていることを確認します。DNS:envoy.home.labと表示されていることから、サーバ証明書のSANにenvoy.home.labが登録されていることが分かります。

[root@control envoy-tls]# openssl x509 \
  -in envoy.crt \
  -noout \
  -ext subjectAltName
X509v3 Subject Alternative Name:
    DNS:envoy.home.lab

CAによる署名が正しいことを確認します。

[root@control envoy-tls]# openssl verify \
  -CAfile ca.crt \
  envoy.crt
envoy.crt: OK

4 マニフェストの作成

この章では、Envoyをサイドカーとして利用してTLS終端を行うため、ConfigMap、Deployment、Serviceのマニフェストを作成します。TLS Secretについては、5章でサーバ証明書と秘密鍵から直接作成します。

本構成で使用するKubernetesリソースは、次のとおりです。

リソース 本構成での役割
ConfigMap Envoyとnginxが使用する設定内容の管理
Secret EnvoyがTLS通信で使用するサーバ証明書と秘密鍵の管理
Deployment nginxとEnvoyを同じPod内で起動し、設定ファイルや証明書をマウント
Service EnvoyのHTTPSポートへアクセスするための接続先の提供

4.1 ConfigMapのマニフェスト作成

最初に、Envoyとnginxで使用する設定内容をConfigMapとして作成します。今回のConfigMapには、次の2つの設定ファイルの内容を格納します。

設定ファイル名 用途
envoy.yaml Envoyが8443番ポートでHTTPS通信を受け付け、TLS終端後のHTTP通信をnginxへ転送するための設定
nginx.conf nginxが127.0.0.1の8080番ポートでHTTP通信を受け付けるための設定

ConfigMapを定義するマニフェストを作成します。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: envoy-sidecar-config
data:
  envoy.yaml: |
    static_resources:
      listeners:
      - name: https_listener
        address:
          socket_address:
            address: 0.0.0.0
            port_value: 8443
        filter_chains:
        - filters:
          - name: envoy.filters.network.http_connection_manager
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
              stat_prefix: ingress_http
              route_config:
                name: local_route
                virtual_hosts:
                - name: nginx
                  domains:
                  - "*"
                  routes:
                  - match:
                      prefix: "/"
                    route:
                      cluster: nginx
              http_filters:
              - name: envoy.filters.http.router
                typed_config:
                  "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router

          transport_socket:
            name: envoy.transport_sockets.tls
            typed_config:
              "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
              common_tls_context:
                tls_certificates:
                - certificate_chain:
                    filename: /etc/envoy/tls/tls.crt
                  private_key:
                    filename: /etc/envoy/tls/tls.key

      clusters:
      - name: nginx
        type: STATIC
        connect_timeout: 1s
        load_assignment:
          cluster_name: nginx
          endpoints:
          - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: 127.0.0.1
                    port_value: 8080

  nginx.conf: |
    events {}

    http {
        server {
            listen 127.0.0.1:8080;

            location / {
                return 200 "Hello Envoy Sidecar\n";
            }
        }
    }

Envoyは8443番ポートでHTTPS通信を待ち受けます。
transport_socketにはTLS設定を指定し、Secretからマウントするtls.crtとtls.keyを使って、EnvoyでHTTPS通信を終端します。復号されたHTTPリクエストは、Envoyの転送先として設定した、同じPod内のNGINX(127.0.0.1:8080)へ転送されます。

4.2 Deploymentのマニフェスト作成

次に、nginxコンテナとEnvoyコンテナを同じPod内で起動するDeploymentを作成します。ConfigMapに格納したnginx.confとenvoy.yamlをそれぞれのコンテナへマウントします。また、TLS Secretに格納したサーバ証明書と秘密鍵をEnvoyコンテナの/etc/envoy/tlsへマウントします。

[root@control ~]# vi deployment.yaml
[root@control ~]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: envoy-sidecar
spec:
  replicas: 1
  selector:
    matchLabels:
      app: envoy-sidecar
  template:
    metadata:
      labels:
        app: envoy-sidecar
    spec:
      volumes:
        - name: config
          configMap:
            name: envoy-sidecar-config
        - name: tls
          secret:
            secretName: envoy-tls

      containers:
        - name: nginx
          image: nginx:1.30.4
          imagePullPolicy: IfNotPresent
          volumeMounts:
            - name: config
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
              readOnly: true

        - name: envoy
          image: envoyproxy/envoy:v1.38.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8443
          volumeMounts:
            - name: config
              mountPath: /etc/envoy/envoy.yaml
              subPath: envoy.yaml
              readOnly: true
            - name: tls
              mountPath: /etc/envoy/tls
              readOnly: true

このDeploymentでは、1つのPod内で次の2つのコンテナを起動します。

コンテナ 役割
nginx HTTPリクエストを処理するメインコンテナ
Envoy HTTPS通信を受け付け、TLS終端を行うサイドカーコンテナ

Envoyが使用するサーバ証明書と秘密鍵は、envoy-tls Secretを/etc/envoy/tlsへマウントすることで、次のファイルとして参照できます。

/etc/envoy/tls/tls.crt
/etc/envoy/tls/tls.key

4.3 Serviceのマニフェスト作成

最後に、EnvoyへアクセスするためのServiceを作成します。今回はクラスタ内部からHTTPS通信を確認するため、ClusterIP型のServiceを使用します。

Serviceの443番ポートへ送られた通信を、Pod内のEnvoyが待ち受けている8443番ポートへ転送します。

[root@control ~]# vi service.yaml
[root@control ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
  name: envoy-sidecar
spec:
  selector:
    app: envoy-sidecar
  ports:
    - name: https
      port: 443
      targetPort: 8443
  type: ClusterIP

portにはServiceが受け付ける443番ポート、targetPortにはEnvoyコンテナがHTTPS通信を待ち受ける8443番ポートを指定しています。

5 Kubernetesリソースの作成

ここでは、4章で作成したConfigMap、Deployment、Serviceの各マニフェストをKubernetesクラスタへ適用します。また、事前準備で作成したサーバ証明書と秘密鍵からTLS Secretを作成し、必要なリソースが正常に作成されていることを確認します。

5.1 ConfigMapの作成

ConfigMapのマニフェストをKubernetesクラスタへ適用し、Envoyとnginxで使用する設定内容をConfigMapとして作成します。

[root@control ~]# kubectl apply -f configmap.yaml
configmap/envoy-sidecar-config created

ConfigMapが作成されていることを確認します。envoy-sidecar-configのDATAが2となっていることから、ConfigMapにenvoy.yamlとnginx.confの2つの設定内容が格納されていることが分かります。

[root@control ~]# kubectl get configmap
NAME                   DATA   AGE
envoy-sidecar-config   2      21s
kube-root-ca.crt       1      3d6h

5.2 TLS Secretの作成

EnvoyがTLS通信で使用するサーバ証明書と秘密鍵を、KubernetesのTLS Secretとして作成します。事前準備で作成したenvoy.crtとenvoy.keyを指定し、kubectl create secret tlsコマンドを実行します。

[root@control ~]# kubectl create secret tls envoy-tls --cert=/root/envoy-tls/envoy.crt --key=/root/envoy-tls/envoy.key
secret/envoy-tls created

TLS Secretが作成されていることを確認します。

[root@control ~]# kubectl get secrets
NAME        TYPE                DATA   AGE
envoy-tls   kubernetes.io/tls   2      22s

TYPEがkubernetes.io/tls、DATAが2となっていることから、サーバ証明書と秘密鍵の2つのデータが格納されていることが分かります。このTLS Secretは、後ほどDeploymentからEnvoyコンテナの/etc/envoy/tlsへマウントします。

5.3 Deploymentの作成

DeploymentのマニフェストをKubernetesクラスタへ適用します。

[root@control ~]# kubectl apply -f deployment.yaml
deployment.apps/envoy-sidecar created

Deploymentが作成されていることを確認します。READYが1/1、AVAILABLEが1となっていることから、Deploymentが要求する1個のPodがReady状態で利用可能になっていることが分かります。

[root@control ~]# kubectl get deployment
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
envoy-sidecar   1/1     1            1           29s

READYが2/2、STATUSがRunningとなっていることから、Pod内のnginxコンテナとEnvoyコンテナの両方がReady状態で動作していることが分かります。

[root@control ~]# kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
envoy-sidecar-6cfbd4b854-7wqvv   2/2     Running   0          56s   10.244.189.69   worker2   <none>           <none>

5.4 Serviceの作成

ServiceのマニフェストをKubernetesクラスタへ適用し、EnvoyへアクセスするためのClusterIP型Serviceを作成します。

[root@control ~]# kubectl apply -f service.yaml
service/envoy-sidecar created

Serviceが作成されていることを確認します。

[root@control ~]# kubectl get svc
NAME            TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
envoy-sidecar   ClusterIP   10.99.237.207   <none>        443/TCP   30s
kubernetes      ClusterIP   10.96.0.1       <none>        443/TCP   3d6h

6 HTTPS通信の確認

Envoyサイドカーを経由してHTTPS通信が正常に行えることを確認します。ここでは、envoy.home.labをServiceのClusterIPへ名前解決できるように設定した後、curlコマンドでHTTPSアクセスします。

6.1 名前解決の設定

envoy.home.labをServiceのClusterIPへ名前解決できるように、/etc/hostsへ登録します。

[root@control ~]# vi /etc/hosts

Serviceに割り当てられたClusterIPとホスト名を追加します。これにより、envoy.home.labへのアクセスはServiceのClusterIPである10.99.237.207へ送信されます。

[root@control ~]# cat /etc/hosts
-snip-
10.99.237.207 envoy.home.lab

6.2 HTTPS通信の確認

curlコマンドを使用して、envoy.home.labへHTTPSでアクセスします。--cacertには、envoyのサーバ証明書を署名した検証用CAの証明書を指定します。また、-vを指定してTLS接続や証明書検証の詳細を表示します。

[root@control ~]# curl -v --cacert /root/envoy-tls/ca.crt https://envoy.home.lab/
* Host envoy.home.lab:443 was resolved.
* IPv6: (none)
* IPv4: 10.99.237.207
*   Trying 10.99.237.207:443...
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
*  CAfile: /root/envoy-tls/ca.crt
*  CApath: none
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Unknown (25):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / x25519 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
*  subject: C=JP; ST=Tokyo; L=Tokyo; O=Example; OU=Example; CN=envoy.home.lab
*  start date: Aug  7 05:10:37 2026 GMT
*  expire date: Aug  7 05:10:37 2027 GMT
*  subjectAltName: host "envoy.home.lab" matched cert's "envoy.home.lab"
*  issuer: C=JP; ST=Tokyo; L=Tokyo; O=Example; OU=Example; CN=Envoy Test CA
*  SSL certificate verify ok.
*   Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption
* Connected to envoy.home.lab (10.99.237.207) port 443
* using HTTP/1.x
> GET / HTTP/1.1
> Host: envoy.home.lab
> User-Agent: curl/8.12.1
> Accept: */*
>
* Request completely sent off
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
< HTTP/1.1 200 OK
< server: envoy
< date: Sun, 09 Aug 2026 22:40:12 GMT
< content-type: text/plain
< content-length: 20
< x-envoy-upstream-service-time: 0
<
Hello Envoy Sidecar
* Connection #0 to host envoy.home.lab left intact

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍

Kubernetesのサイドカーを試す~Fluent Bitによるログ収集

1 はじめに

Kubernetesでは、1つのPod内で複数のコンテナを動作させることができます。このうち、メインコンテナを補助する役割を持つコンテナをサイドカーと呼びます。サイドカーを利用すると、アプリケーション本体を大きく変更することなく、ログ収集や通信制御などの機能を追加できます。

本記事では、nginxを実行するPodにFluent Bitをサイドカーとして追加し、nginxのアクセスログを収集して、Fluent Bitコンテナの標準出力へ出力します。出力されたログは、kubectl logsコマンドで確認します。

この記事を通して、以下の内容を理解することを目標とします。

  • サイドカーとは何か、およびKubernetesで利用される目的
  • 1つのPod内でnginxコンテナとFluent Bitコンテナを同時に動作させる方法
  • コンテナ間でVolumeを共有し、Fluent Bitがnginxのログを収集する仕組み
  • Fluent Bitが収集したログを標準出力へ出力し、正常に動作していることを確認する方法

なお、実際の運用ではFluent Bitが収集したログは、LokiやElasticsearchなどのログ基盤へ転送して集約・検索する構成が一般的です。本記事ではサイドカーの基本的な仕組みの理解に焦点を当て、ログ基盤との連携については次回以降の記事で、LokiとGrafanaを利用したログの集約・可視化を行う予定です。

Fluent Bitの詳しい説明は、以下のページをご覧ください。
fluentbit.io

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@control ~]# kubectl version
Client Version: v1.36.3
Kustomize Version: v5.8.1
Server Version: v1.36.3

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードには4つの vCPUを割り当てています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 マニフェストの作成

この章では、サイドカー構成を構築するため、次の3つのKubernetesリソースのマニフェストを作成します。

リソース 本構成での役割
ConfigMap nginxとFluent Bitが使用する設定ファイルの管理
Deployment nginxコンテナとFluent Bitコンテナの起動、およびログを保存するVolumeの共有
Service nginxコンテナへアクセスするための接続先の提供

3.1 ConfigMapのマニフェスト作成

nginxとFluent Bitで使用する設定内容をConfigMapに格納します。ConfigMapは、コンテナで使用する設定値や設定ファイルの内容をKubernetesリソースとして管理するための仕組みです。今回は、nginxとFluent Bitの設定内容をConfigMapに格納し、後ほどDeploymentのPodテンプレートで、各コンテナ内へ設定ファイルとしてマウントします。

今回のConfigMapには、次の2つの設定ファイルの内容を格納します。

設定ファイル名 用途
nginx.conf nginxの設定ファイルです。アクセスログを/var/log/nginx/access.log、エラーログを/var/log/nginx/error.logへ出力するよう設定します
fluent-bit.conf Fluent Bitの設定ファイルです。/var/log/nginx/access.logを監視し、取得したログを標準出力(stdout)へ出力するよう設定します

今回の構成では、nginxとFluent Bitを同じPod内で動作させ、DeploymentのPodテンプレートで定義したemptyDir Volumeを両方のコンテナで共有します。nginxが共有Volumeへ出力したアクセスログをFluent Bitが読み取り、標準出力へ出力します。

nginx
  |
  | /var/log/nginx に書き込み
  v
nginx-log(emptyDir)
  | /var/log/nginx から読み取り
  |
  v
Fluent Bit

emptyDirの詳細は以下の記事を参照してください。
hana-shin.hatenablog.com

ConfigMapのマニフェストを作成します。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-fluentbit-config
data:
  fluent-bit.conf: |
    [SERVICE]
        Flush        1
        Daemon       Off
        Log_Level    info

    [INPUT]
        Name              tail
        Path              /var/log/nginx/access.log
        Tag               nginx.access
        Refresh_Interval  1

    [OUTPUT]
        Name   stdout
        Match  *

  nginx.conf: |
    events {}

    http {
        access_log /var/log/nginx/access.log;
        error_log  /var/log/nginx/error.log;

        server {
            listen 80;

            location / {
                return 200 "Hello Fluent Bit Sidecar\n";
            }
        }
    }

3.2 Deploymentのマニフェスト作成

次に、nginxコンテナとFluent Bitコンテナを同じPod内で起動するDeploymentを作成します。このDeploymentでは、次の2つのVolumeを定義します。

Volume名 用途
nginx-log nginxのログを保存し、Fluent Bitと共有するemptyDir
config ConfigMapに格納した設定ファイルを各コンテナへ提供するVolume

nginx-logを両方のコンテナの/var/log/nginxへマウントすることで、nginxが書き込んだログをFluent Bitから読み取れるようにします。

Deploymentのマニフェストを作成します。

[root@control ~]# vi deployment.yaml
[root@control ~]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-sidecar
spec:
  replicas: 1

  selector:
    matchLabels:
      app: nginx-sidecar

  template:
    metadata:
      labels:
        app: nginx-sidecar

    spec:
      volumes:

      - name: nginx-log
        emptyDir: {}

      - name: config
        configMap:
          name: nginx-fluentbit-config

      containers:

      - name: nginx
        image: nginx:1.30.4
        imagePullPolicy: IfNotPresent

        ports:
        - containerPort: 80

        volumeMounts:

        - name: nginx-log
          mountPath: /var/log/nginx

        - name: config
          mountPath: /etc/nginx/nginx.conf
          subPath: nginx.conf

      - name: fluent-bit
        image: fluent/fluent-bit:5.0.9
        imagePullPolicy: IfNotPresent

        volumeMounts:

        - name: nginx-log
          mountPath: /var/log/nginx

        - name: config
          mountPath: /fluent-bit/etc/fluent-bit.conf
          subPath: fluent-bit.conf

3.3 Serviceのマニフェスト作成

最後に、nginxへアクセスするためのServiceを作成します。Serviceを利用すると、PodのIPアドレスを直接指定せず、Serviceに割り当てられたClusterIPを使用してPodへアクセスできます。今回はクラスタ外部へ公開しないため、ClusterIP型のServiceを使用します。

Serviceのマニフェストを作成します。

[root@control ~]# vi service.yaml
[root@control ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-sidecar
spec:
  selector:
    app: nginx-sidecar

  ports:
    - port: 80
      targetPort: 80

  type: ClusterIP

4 動作確認

ここでは、作成したConfigMap、Deployment、Serviceの各マニフェストをKubernetesクラスタへ適用し、サイドカー構成が正常に動作することを確認します。

(1) ConfigMapの作成
nginxとFluent Bitの設定ファイルを格納したConfigMapを作成します。

[root@control ~]# kubectl apply -f configmap.yaml

ConfigMapが作成されていることを確認します。nginx-fluentbit-configのDATA列が2となっていることから、nginx.confとfluent-bit.confの2つの設定データが格納されていることが分かります。

[root@control ~]# kubectl get configmaps
NAME                     DATA   AGE
kube-root-ca.crt         1      47h
nginx-fluentbit-config   2      12s

(2) Deploymentの作成

次に、nginxコンテナとFluent Bitコンテナを同じPod内で起動するDeploymentを作成します。

[root@control ~]#  kubectl apply -f deployment.yaml

Deploymentが作成されていることを確認します。READYが1/1、AVAILABLEが1となっていることから、Deploymentが管理する1個のPodがReady状態で利用可能になっていることが分かります。

[root@control ~]# kubectl get deployments
NAME            READY   UP-TO-DATE   AVAILABLE   AGE
nginx-sidecar   1/1     1            1           60s

Podが正常に起動していることを確認します。

READYが2/2、STATUSがRunningとなっていることから、Pod内のnginxコンテナとFluent Bitコンテナの両方がReady状態で動作していることが分かります。また、IP列にはPodへ割り当てられたPod IP(10.244.189.68)が表示されています。

[root@control ~]# kubectl get pods -o wide
NAME                             READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
nginx-sidecar-7674444f57-6hmkg   2/2     Running   0          19m   10.244.189.68   worker2   <none>           <none>

(3) Serviceの作成
続いて、nginxへアクセスするためのServiceを作成します。今回は外部公開を行わず、クラスタ内部からアクセスするため、ClusterIP型のServiceを使用します。

[root@control ~]# kubectl apply -f service.yaml

Serviceが正常に作成されていることを確認します。TYPEがClusterIPとなっていることから、このServiceはクラスタ内部からアクセスするための仮想IPを提供していることが分かります。今回はClusterIPとして 10.101.151.199 が割り当てられています。EXTERNAL-IPが<none>となっているのは、このServiceがClusterIP型であり、クラスタ外部へ公開するためのIPアドレスが割り当てられていないためです。外部公開する場合はServiceをLoadBalancer型に変更し、オンプレミス環境ではMetalLB、クラウド環境ではクラウドプロバイダのロードバランサーを利用することで、EXTERNAL-IPにIPアドレスが割り当てられます。

[root@control ~]# kubectl get svc
NAME            TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
kubernetes      ClusterIP   10.96.0.1        <none>        443/TCP   2d
nginx-sidecar   ClusterIP   10.101.151.199   <none>        80/TCP    4s

Service経由でnginxへアクセスし、正常に応答することを確認します。Hello Fluent Bit Sidecarが表示されていることから、controlノードからServiceのClusterIPを経由してnginxへ正常にアクセスできることが確認できます。

[root@control ~]# curl http://10.101.151.199
Hello Fluent Bit Sidecar

curlコマンドが復帰しない場合、以下の対処をしてみてください。
hana-shin.hatenablog.com

続いて、nginxがアクセスログを出力していることを確認します。アクセスログが表示されることから、nginxが/var/log/nginx/access.logへ正常にログを書き込んでいることが分かります。このログファイルは、nginxとFluent Bitが共有するemptyDir上に保存されています。

[root@control ~]# kubectl exec -it nginx-sidecar-7674444f57-6hmkg -c nginx -- cat /var/log/nginx/access.log
10.244.42.192 - - [06/Aug/2026:01:32:02 +0000] "GET / HTTP/1.1" 200 25 "-" "curl/8.12.1"
10.244.42.192 - - [06/Aug/2026:01:33:24 +0000] "GET / HTTP/1.1" 200 25 "-" "curl/8.12.1"

最後にFluent Bitコンテナのログを確認します。

[root@control ~]# kubectl logs nginx-sidecar-7674444f57-6hmkg -c fluent-bit
Fluent Bit v5.0.9
* Copyright (C) 2015-2026 The Fluent Bit Authors
* Fluent Bit is a CNCF graduated project under the Fluent organization
* https://fluentbit.io

______ _                  _    ______ _ _           _____  _____
|  ___| |                | |   | ___ (_) |         |  ___||  _  |
| |_  | |_   _  ___ _ __ | |_  | |_/ /_| |_  __   _|___ \ | |/' |
|  _| | | | | |/ _ \ '_ \| __| | ___ \ | __| \ \ / /   \ \|  /| |
| |   | | |_| |  __/ | | | |_  | |_/ / | |_   \ V //\__/ /\ |_/ /
\_|   |_|\__,_|\___|_| |_|\__| \____/|_|\__|   \_/ \____(_)\___/


[2026/08/06 01:22:49.311] [ info] [fluent bit] version=5.0.9, commit=a1e05fc1f7, pid=1
[2026/08/06 01:22:49.311] [ info] [storage] ver=1.5.4, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2026/08/06 01:22:49.311] [ info] [simd    ] SSE2
[2026/08/06 01:22:49.311] [ info] [cmetrics] version=2.1.5
[2026/08/06 01:22:49.312] [ info] [ctraces ] version=0.7.1
[2026/08/06 01:22:49.313] [ info] [input:tail:tail.0] initializing
[2026/08/06 01:22:49.313] [ info] [input:tail:tail.0] storage_strategy='memory' (memory only)
[2026/08/06 01:22:49.314] [ info] [sp] stream processor started
[2026/08/06 01:22:49.314] [ info] [engine] Shutdown Grace Period=5, Shutdown Input Grace Period=2
[2026/08/06 01:22:49.314] [ info] [input:tail:tail.0] inotify_fs_add(): inode=35432256 watch_fd=1 name=/var/log/nginx/access.log
[2026/08/06 01:22:49.370] [ info] [output:stdout:stdout.0] worker #0 started
[0] nginx.access: [[1785979922.245445024, {}], {"log"=>"10.244.42.192 - - [06/Aug/2026:01:32:02 +0000] "GET / HTTP/1.1" 200 25 "-" "curl/8.12.1""}]
[0] nginx.access: [[1785980004.014186290, {}], {"log"=>"10.244.42.192 - - [06/Aug/2026:01:33:24 +0000] "GET / HTTP/1.1" 200 25 "-" "curl/8.12.1""}]
[root@control ~]#

5 まとめ

本記事では、Kubernetesのサイドカーを理解することを目的として、nginxとFluent Bitを同じPod内で動作させる構成を構築しました。今回学んだポイントは以下のとおりです。

  • サイドカーは、メインコンテナの機能を補助・拡張するためのコンテナであること
  • 1つのPod内では複数のコンテナを同時に実行できること
  • emptyDirを利用することで、コンテナ間でファイルを共有できること
  • Fluent Bitは、共有Volume上のアクセスログを監視し、ログを収集できること
  • 収集したログは、標準出力へ出力できるため、kubectl logsで確認できること

今回はサイドカーの基本的な仕組みを理解することを目的としたため、Fluent Bitの出力先は標準出力としました。実際の運用では、Fluent BitはLokiやElasticsearchなどのログ基盤へログを転送し、複数のPodやノードから収集したログを一元的に管理します。

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍

Kubernetes HPAでCPU/メモリ使用率に応じたPodの自動スケーリングを検証してみた

1 オートスケーリングとは

Kubernetes では、負荷に応じて リソースを自動的に調整する仕組みがあります。これを「オートスケーリング」と呼びます。たとえば、アクセスが急増すると処理が遅くなったり、逆にアクセスが少ないときはリソースが無駄になったりします。オートスケーリングを利用することで、こうした状況に応じてリソースを自動的に増減し、安定した動作と効率的な運用を実現できます。Kubernetes のオートスケーリングには、大きく分けて次の 2 種類があります。

種類 概要 用途
HPA(Horizontal Pod Autoscaler:水平スケーリング) CPU・メモリのメトリクスをもとに Pod の数を自動で調整する仕組み アクセスの増減に応じて負荷分散したい場合
VPA(Vertical Pod Autoscaler:垂直スケーリング) Pod に割り当てる CPU・メモリを自動で調整する仕組み リソース不足や過剰を自動で最適化したい場合

この記事では、HPA について説明します。なお、HPA は CPU・メモリのメトリクスをもとにスケーリングを判断するため、その情報を提供する Metrics Server も合わせて構築します。また、Prometheus Adapter などを導入すれば、CPU・メモリ以外のメトリクス(ネットワーク帯域など)をもとにスケーリングすることも可能ですが、本記事では扱いません。

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@contol ~]# kubectl version
Client Version: v1.36.3
Kustomize Version: v5.8.1
Server Version: v1.36.3

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

HPA は Metrics Server が提供する CPU・メモリなどのメトリクスを基にスケーリングを判断するため、Metrics Server をインストールします。詳細は以下の記事を参照してください。

なお、以下の記事では Metrics Server を kube-system Namespace にインストールしていますが、本記事では default Namespace にインストールしています。システム用の Pod は kube-system Namespace にインストールした方が管理上シンプルですが、HPA の動作確認が目的であれば、どちらの Namespace にインストールしても問題ありません。
hana-shin.hatenablog.com

4 CPU負荷によるHPAの動作確認

本章では、HPA が CPU 使用率に応じて Pod 数を自動的に増減させる動作を検証します。具体的には、stress-ng を用いて Pod に意図的な CPU 負荷をかけ、以下の挙動を確認します。

  • CPU使用率が閾値(50%)を超えた際に、HPAがPod数を増加させること
  • 負荷が収まった際に一定時間(30秒)待って、HPAがPod数を削減させること

4.1 マニフェスト作成

4.1.1 HPAのマニフェスト作成

CPU使用率に応じてPod数を自動調整するHPAのマニフェストを作成します。

  • CPU 使用率の平均が 50% を超えた場合にPod 数を増加
  • CPU 使用率の平均が 50% を下回った場合にPod 数を減少
  • Pod 数は最小 1、最大 3 の範囲で自動調整
  • stabilizationWindowSeconds は、過去のメトリクスを一定時間考慮してからスケーリングを実行する待機時間
  • Pod数の増加は即時(0秒)実行
  • Pod数の減少は、頻繁な増減を防ぐため 30 秒待ってから実行
[root@control ~]# vi autoscaling-cpu.yaml
[root@control ~]# cat autoscaling-cpu.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: almalinux-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: almalinux-hpa
  minReplicas: 1
  maxReplicas: 3

  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
    scaleDown:
      stabilizationWindowSeconds: 30

  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
4.1.2 Deployment のマニフェスト作成

Pod を管理するためのDeployment のマニフェストを作成します。なお、マニフェスト中のresources セクションでは、Pod に割り当てる CPU・メモリのリソース量を requests と limits という 2つの項目で指定します。requests はコンテナが動作するために最低限必要とするリソース量、limits はコンテナが使用できる上限のリソース量を指定します。

[root@control ~]# vi almalinux-cpu.yaml
[root@control ~]# cat almalinux-cpu.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: almalinux-hpa
spec:
  replicas: 1
  selector:
    matchLabels:
      app: almalinux-hpa
  template:
    metadata:
      labels:
        app: almalinux-hpa
    spec:
      containers:
      - name: almalinux
        image: almalinux:9
        command: ["/bin/bash", "-c", "sleep infinity"]
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"

4.2 クラスタへのマニフェスト適用

HPAのマニフェストをクラスタに適用します。

[root@control ~]# kubectl apply -f autoscaling-cpu.yaml
horizontalpodautoscaler.autoscaling/almalinux-hpa created

HPAの状態を確認します。TARGETS の <unknown>は、まだ Deployment が作成されていないため Pod が存在せず、メトリクスが取得できていない状態です。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS              MINPODS   MAXPODS   REPLICAS   AGE
almalinux-hpa   Deployment/almalinux-hpa   cpu: <unknown>/50%   1         3         0          9s

次に、Deploymentのマニフェストをクラスタに適用します。

[root@control ~]# kubectl apply -f almalinux-cpu.yaml
deployment.apps/almalinux-hpa created

Podが起動していることを確認します。

[root@control ~]#  kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-hpa-7b574d655d-2n5xw    1/1     Running   0          11s   10.244.235.157   worker1   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          37m   10.244.235.154   worker1   <none>           <none>

4.3 動作確認

起動中のPodにログインします。

[root@control ~]# kubectl exec -it almalinux-hpa-7b574d655d-2n5xw -- /bin/bash
[root@almalinux-hpa-7b574d655d-2n5xw /]#

CPUに負荷をかけるための stress-ng パッケージをインストールします。

[root@almalinux-hpa-7b574d655d-2n5xw /]# dnf -y install stress-ng

stress-ng コマンドの詳細な使い方は以下を参照してください。
hana-shin.hatenablog.com

stress-ng でCPU使用率を意図的に上昇させます。CPUを1コア使用し、使用率60%の負荷を300秒間かけます。

[root@almalinux-hpa-7b574d655d-2n5xw /]# stress-ng --cpu 1 --cpu-load 60 --timeout 300s
stress-ng: info:  [60] setting to a 5 mins run per stressor
stress-ng: info:  [60] dispatching hogs: 1 cpu
stress-ng: info:  [61] cpu: for stable load results, select a specific cpu stress method with --cpu-method other than 'all'

CPUの requests が 359% となり、閾値の 50% を超えたため、HPA により Pod 数(REPLICAS)が 3 にスケールアウトされたことが確認できます。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS         MINPODS   MAXPODS   REPLICAS   AGE
almalinux-hpa   Deployment/almalinux-hpa   cpu: 359%/50%   1         3         3          2m28s

kubectl get pods コマンドで確認すると、 Pod が 3 つにスケールアウトされていることが分かります。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE    IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-hpa-7b574d655d-2n5xw    1/1     Running   0          2m9s   10.244.235.157   worker1   <none>           <none>
almalinux-hpa-7b574d655d-fspqg    1/1     Running   0          41s    10.244.235.159   worker1   <none>           <none>
almalinux-hpa-7b574d655d-x6pdr    1/1     Running   0          41s    10.244.189.76    worker2   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          39m    10.244.235.154   worker1   <none>           <none>

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

[root@almalinux-hpa-7b574d655d-2n5xw /]# stress-ng --cpu 1 --cpu-load 60 --timeout 300s
stress-ng: info:  [60] setting to a 5 mins run per stressor
stress-ng: info:  [60] dispatching hogs: 1 cpu
stress-ng: info:  [61] cpu: for stable load results, select a specific cpu stress method with --cpu-method other than 'all'
^Cstress-ng: info:  [60] skipped: 0
stress-ng: info:  [60] passed: 1: cpu (1)
stress-ng: info:  [60] failed: 0
stress-ng: info:  [60] metrics untrustworthy: 0
stress-ng: info:  [60] successful run completed in 49.21 secs

終了直後は、まだPodが3つ動作していることが確認できます。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-hpa-7b574d655d-2n5xw    1/1     Running   0          2m34s   10.244.235.157   worker1   <none>           <none>
almalinux-hpa-7b574d655d-fspqg    1/1     Running   0          66s     10.244.235.159   worker1   <none>           <none>
almalinux-hpa-7b574d655d-x6pdr    1/1     Running   0          66s     10.244.189.76    worker2   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          39m     10.244.235.154   worker1   <none>           <none>

しばらくすると負荷が下がり、CPU使用率が 0% になったことが確認できます。ただし、マニフェストで設定した待機時間(30秒)が経過していないため、この時点ではまだ Pod 数(REPLICAS)は 3 のまま維持されています。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS       MINPODS   MAXPODS   REPLICAS   AGE
almalinux-hpa   Deployment/almalinux-hpa   cpu: 0%/50%   1         3         3          3m49s

さらに待機時間が経過した後に Pod 一覧を確認すると、スケールインが完了し、Pod 数が元の 1 つに戻ったことが確認できます。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-hpa-7b574d655d-x6pdr    1/1     Running   0          2m35s   10.244.189.76    worker2   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          41m     10.244.235.154   worker1   <none>           <none>

4.4 あと始末

次の検証のため、ラスタに適用したDeploymentを削除します。

[root@control ~]# kubectl delete -f almalinux-cpu.yaml
deployment.apps "almalinux-hpa" deleted from default namespace

続けて、HPA のリソースも削除します。

[root@control ~]# kubectl delete -f autoscaling-cpu.yaml
horizontalpodautoscaler.autoscaling "almalinux-hpa" deleted from default namespace

5 メモリ負荷によるHPAの動作確認

本章では、HPA が メモリ使用率 に応じて Pod 数を自動的に増減させる動作を検証します。4章では CPU 使用率を対象としましたが、本章では stress-ng でメモリに負荷をかけ、メモリ使用率が閾値(70%)を超えた際に HPA が Pod 数を増加させること、また負荷が収まった後に一定時間(30秒)待ってからPod 数が減少することを確認します。

5.1 マニフェスト作成

5.1.1 HPAのマニフェスト作成

メモリ使用率に応じてPod数を自動調整するHPAのマニフェストを作成します。

[root@control ~]# vi autoscaling-mem.yaml
[root@control ~]# cat autoscaling-mem.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: almalinux-mem
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: almalinux-mem
  minReplicas: 1
  maxReplicas: 3

  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
    scaleDown:
      stabilizationWindowSeconds: 30

  metrics:
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70
5.1.2 Deployment のマニフェスト作成

Pod を管理するためのDeployment のマニフェストを作成します。

[root@control ~]# vi almalinux-mem.yaml
[root@control ~]# cat almalinux-mem.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: almalinux-mem
spec:
  replicas: 1
  selector:
    matchLabels:
      app: almalinux-mem
  template:
    metadata:
      labels:
        app: almalinux-mem
    spec:
      containers:
      - name: almalinux
        image: almalinux:9
        command: ["/bin/bash", "-c", "sleep infinity"]
        resources:
          requests:
            cpu: "100m"
            memory: "128Mi"
          limits:
            cpu: "500m"
            memory: "256Mi"

5.2 クラスタへのマニフェスト適用

HPAのマニフェストをクラスタに適用します。

[root@control ~]# kubectl apply -f autoscaling-mem.yaml
horizontalpodautoscaler.autoscaling/almalinux-mem created

HPAの状態を確認します。この時点では Pod を起動していないため、使用率は <unknown>と表示されます。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS                 MINPODS   MAXPODS   REPLICAS   AGE
almalinux-mem   Deployment/almalinux-mem   memory: <unknown>/70%   1         3         0          9s 6s

次に、Deploymentのマニフェストをクラスタに適用します。

[root@control ~]# kubectl apply -f almalinux-mem.yaml
deployment.apps/almalinux-mem created

Podが起動していることを確認します。Podが正常に起動し、リソース監視用のmetrics-serverが動いていることを確認します。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-mem-79597884cb-kf8fv    1/1     Running   0          21s   10.244.235.161   worker1   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          70m   10.244.235.154   worker1   <none>           <none>

5.3 動作確認

負荷をかけるために、起動したPodのコンテナ内へログインします。

[root@control ~]# kubectl exec -it almalinux-mem-79597884cb-kf8fv -- /bin/bash
[root@almalinux-mem-79597884cb-kf8fv /]#

stress-ng パッケージをインストールします。

[root@almalinux-mem-79597884cb-kf8fv /]# dnf -y install stress-ng

stress-ngコマンドを使い、メモリを200MB消費させて意図的に負荷を与えます。

[root@almalinux-mem-79597884cb-kf8fv /]# stress-ng --vm 1 --vm-bytes 200M --vm-hang 300
stress-ng: info:  [60] defaulting to a 1 day run per stressor
stress-ng: info:  [60] dispatching hogs: 1 vm

もうひとつターミナルを開きます。kubectl get hpa コマンドを実行して、HPA が負荷を検知し、TARGETS の値がしきい値(70%)を超えたことを確認します。ただしこの時点ではまだ REPLICAS の値には反映されておらず、Pod数が増えるまでには少し時間がかかります。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS            MINPODS   MAXPODS   REPLICAS   AGE
almalinux-mem   Deployment/almalinux-mem   memory: 169%/70%   1         3         1          2m48s

オートスケーリングが発動し、Pod数が自動的に最大値(3台)まで増えていることを確認します。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-mem-79597884cb-jvmm7    1/1     Running   0          15s     10.244.235.162   worker1   <none>           <none>
almalinux-mem-79597884cb-kf8fv    1/1     Running   0          2m39s   10.244.235.161   worker1   <none>           <none>
almalinux-mem-79597884cb-kzsbt    1/1     Running   0          15s     10.244.189.77    worker2   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          72m     10.244.235.154   worker1   <none>           <none>

stress-ng の負荷テストを停止させた後、しばらく待ちます。メモリ使用率が 0% に低下し、さらに設定した「30秒」の待機時間(stabilizationWindowSeconds)が経過すると、HPA の REPLICAS が元の 1 に戻ったことが確認できます。

[root@control ~]# kubectl get hpa
NAME            REFERENCE                  TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
almalinux-mem   Deployment/almalinux-mem   memory: 0%/70%   1         3         1          14m

実際の Pod 一覧を確認すると、負荷が下がったことで Pod 数が減り、1台のみ稼働している状態に戻っていることが分かります。

[root@control ~]# kubectl get pods -o wide
NAME                              READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
almalinux-mem-79597884cb-kzsbt    1/1     Running   0          12m   10.244.189.77    worker2   <none>           <none>
metrics-server-644bf9cbdb-sn9lr   1/1     Running   0          85m   10.244.235.154   worker1   <none>           <none>

5.4 あと始末

検証が終わったら、次のテストのためにマニフェストを指定してクラスター上の Deployment リソースを削除します。

[root@control ~]# kubectl delete -f almalinux-mem.yaml
deployment.apps "almalinux-mem" deleted from default namespace

最後に、HPA のリソースも削除します。

[root@control ~]# kubectl delete -f autoscaling-mem.yaml
horizontalpodautoscaler.autoscaling "almalinux-mem" deleted from default namespace

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍

Kubernetes Metrics Server を導入して kubectl top でノードのリソース使用状況を確認してみる

1 メトリクスサーバーとは?

メトリクスサーバーは、Kubernetes クラスタ内のノードや Pod のリソース使用状況(CPU・メモリ使用率など)を収集するサーバです。収集したメトリクス(システムの状態や性能を数値で表したデータ)は、主に HPA(Horizontal Pod Autoscaler) や VPA(Vertical Pod Autoscaler) などのオートスケーリング機能で利用され、Pod の数やリソース量を自動的に調整するために使われます。
github.com

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@contol ~]# kubectl version
Client Version: v1.36.3
Kustomize Version: v5.8.1
Server Version: v1.36.3

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

3.1 検証ツールのインストール

CPU負荷をあげる検証をするため、stress-ngパッケージをインストールします。

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

stress-ngコマンドのバージョンを確認します。

[root@contol ~]# stress-ng --version
stress-ng, version 0.19.03 (gcc 14.3.1, x86_64 Linux 6.12.0-211.7.3.el10_2.x86_64) ??

stress-ngコマンドの使い方は、以下を参照してください。
hana-shin.hatenablog.com

3.2 Kubelet のサーバ証明書の再作成

Metrics Server は、メトリクスを取得するために、各ノード上で動作している Kubelet に対して HTTPS 接続を行います。

このとき、Kubelet が使用するサーバ証明書には、デフォルトでは自身のノード IP アドレスが Subject Alternative Name(SAN)として設定されていません。

一方、Metrics Server は Kubelet から送信されたサーバ証明書を検証する際、接続先として指定した IP アドレスが証明書の SAN に含まれているかを確認します。しかし、Kubelet のサーバ証明書の SAN にノード IP アドレスが設定されていないため、証明書検証に失敗します。

この問題を解決するため、Kubelet のサーバ証明書を再発行します。再発行することで、サーバ証明書の SAN に各ノードの IP アドレスが設定され、Metrics Server は Kubelet の証明書を正しく検証できるようになります。

3.2.1 全てのノードで実施

まず、設定変更に備えて Kubelet の設定ファイルをバックアップします。

[root@control ~]# cp /var/lib/kubelet/config.yaml /var/lib/kubelet/config.yaml.org

kubeletの設定ファイルを編集します。

[root@control ~]# vi /var/lib/kubelet/config.yaml

設定ファイルの末尾に serverTLSBootstrap: true を追加します。この設定を有効にすると、Kubelet はサーバ証明書の Certificate Signing Request(CSR)を Kubernetes API Server に送信し、Kubernetes CA からサーバ証明書を取得できるようになります。

[root@control ~]# diff -Nur /var/lib/kubelet/config.yaml.org /var/lib/kubelet/config.yaml
--- /var/lib/kubelet/config.yaml.org    2026-08-03 20:20:36.010789515 +0900
+++ /var/lib/kubelet/config.yaml        2026-08-03 20:25:53.212588088 +0900
@@ -46,3 +46,4 @@
 streamingConnectionIdleTimeout: 0s
 syncFrequency: 0s
 volumeStatsAggPeriod: 0s
+serverTLSBootstrap: true

設定を反映するため、Kubelet を再起動します。

[root@control ~]# systemctl restart kubelet
3.2.2 コントロールノードで実施

Kubelet を再起動すると、各ノードの Kubelet は CSR(Certificate Signing Request)を Kubernetes API Server に送信します。CSR の状態を確認すると、CONDITION 列が Pending なので、CSR がまだ承認されておらず、サーバ証明書が発行されていない状態を示しています。

[root@control ~]# kubectl get csr
NAME        AGE   SIGNERNAME                      REQUESTOR             REQUESTEDDURATION   CONDITION
csr-bdgn5   6s    kubernetes.io/kubelet-serving   system:node:worker2   <none>              Pending
csr-ds5zx   8s    kubernetes.io/kubelet-serving   system:node:control   <none>              Pending
csr-n8k2p   12s   kubernetes.io/kubelet-serving   system:node:worker1   <none>              Pending

kubectl certificate approve コマンドを実行して CSR を承認します。承認されると、Kubernetes CA の秘密鍵を使用して CSR に署名し、Kubelet のサーバ証明書を発行します。

[root@control ~]# kubectl certificate approve csr-bdgn5
certificatesigningrequest.certificates.k8s.io/csr-bdgn5 approved
[root@control ~]# kubectl certificate approve csr-ds5zx
certificatesigningrequest.certificates.k8s.io/csr-ds5zx approved
[root@control ~]# kubectl certificate approve csr-n8k2p
certificatesigningrequest.certificates.k8s.io/csr-n8k2p approved

承認後、CSR の状態を確認します。Approved,Issued と表示されれば、Kubernetes CA によってサーバ証明書が発行され、各ノードの Kubelet が新しいサーバ証明書を取得したことを示しています。

[root@control ~]# kubectl get csr
NAME        AGE     SIGNERNAME                      REQUESTOR             REQUESTEDDURATION   CONDITION
csr-bdgn5   4m9s    kubernetes.io/kubelet-serving   system:node:worker2   <none>              Approved,Issued
csr-ds5zx   4m11s   kubernetes.io/kubelet-serving   system:node:control   <none>              Approved,Issued
csr-n8k2p   4m15s   kubernetes.io/kubelet-serving   system:node:worker1   <none>              Approved,Issued
3.2.3 Kubelet サーバ証明書の確認

Kubelet のサーバ証明書に、各ノードの IP アドレスが Subject Alternative Name(SAN)として設定されていることを確認します。serverTLSBootstrap を有効化し、CSR を承認すると、Kubelet は Kubernetes CA によって署名されたサーバ証明書を取得します。この証明書には、Kubelet が待ち受けるノードの DNS 名および IP アドレスが SAN として設定されます。

まず、コントロールノードで Kubelet のサーバ証明書を確認します。

[root@control ~]# openssl x509 -in /var/lib/kubelet/pki/kubelet-server-current.pem -text -noout | grep -A5 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:control, IP Address:192.168.1.15
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        8c:ad:a9:af:7e:16:0b:21:91:69:94:0f:fd:40:ea:49:57:4b:
        75:d5:94:fe:28:89:e0:bf:a6:b2:96:f9:a1:fb:4d:5e:0f:7c:

出力結果から、コントロールノードのホスト名である control と IP アドレス 192.168.1.15 が SAN に設定されていることを確認できます。

次に、ワーカノード(worker1)で Kubelet のサーバ証明書を確認します。

[root@worker1 ~]# openssl x509 -in /var/lib/kubelet/pki/kubelet-server-current.pem -text -noout | grep -A5 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:worker1, IP Address:192.168.1.16
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        7e:f1:47:49:8c:fe:b0:b2:05:3b:5f:2d:12:60:97:48:d5:93:
        61:f9:58:92:72:00:e1:8b:f4:3a:73:74:17:7b:fc:c4:90:48:

同様に、ワーカノード(worker2)でも Kubelet のサーバ証明書を確認します。

[root@worker2 ~]# openssl x509 -in /var/lib/kubelet/pki/kubelet-server-current.pem -text -noout | grep -A5 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:worker2, IP Address:192.168.1.17
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        8c:e3:f8:57:e3:7b:0c:0d:45:89:47:0b:79:1f:bf:02:d5:e5:
        28:4b:90:db:5a:76:92:4c:f7:9d:b4:bc:79:5b:72:99:50:83:

4 メトリクスサーバのインストール

(1) リポジトリの追加
メトリクスサーバーのチャートが公開されているリポジトリを、Helmで利用できるように追加します。

[root@control ~]# helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server
"metrics-server" has been added to your repositories

helmコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

追加したリポジトリの一覧を確認します。

[root@control ~]# helm repo list
NAME            URL
metrics-server  https://kubernetes-sigs.github.io/metrics-serve

ローカルにキャッシュされているチャート情報を最新の状態に更新します。この操作を行わないと、最新のチャート情報がローカルに反映されず、最新バージョンの検索や取得ができない場合があるため、事前に実行します。

[root@contol ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metrics-server" chart repository
Update Complete. ?Happy Helming!?

(2) チャートのインストール
メトリクスサーバーのHelmチャートをクラスタにインストールします。このとき、kube-system 名前空間にデプロイするよう指定します。

[root@control ~]# helm install metrics-server metrics-server/metrics-server --namespace kube-system
NAME: metrics-server
LAST DEPLOYED: Tue Aug  4 14:13:06 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
***********************************************************************
* Metrics Server                                                      *
***********************************************************************
  Chart version: 3.13.1
  App version:   0.8.1
  Image tag:     registry.k8s.io/metrics-server/metrics-server:v0.8.1
***********************************************************************

Podの状態を確認します。コマンドを実行すると、出力結果の一覧に metrics-server のPodが表示され、正常に動作していることを確認できます(この実行例では、一番下の行)。

[root@control ~]# kubectl get pods -n kube-system -o wide
NAME                                       READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
calico-kube-controllers-58f68d56d9-7mh9c   1/1     Running   0          3h47m   10.244.42.194    control   <none>           <none>
calico-node-bw49k                          1/1     Running   0          3h38m   192.168.1.16     worker1   <none>           <none>
calico-node-pmt6s                          1/1     Running   0          3h38m   192.168.1.17     worker2   <none>           <none>
calico-node-x9gmw                          1/1     Running   0          3h47m   192.168.1.15     control   <none>           <none>
coredns-589f44dc88-bm7dp                   1/1     Running   0          3h50m   10.244.42.193    control   <none>           <none>
coredns-589f44dc88-pgrks                   1/1     Running   0          3h50m   10.244.42.195    control   <none>           <none>
etcd-control                               1/1     Running   0          3h50m   192.168.1.15     control   <none>           <none>
kube-apiserver-control                     1/1     Running   0          3h50m   192.168.1.15     control   <none>           <none>
kube-controller-manager-control            1/1     Running   0          3h50m   192.168.1.15     control   <none>           <none>
kube-proxy-hqvsc                           1/1     Running   0          3h38m   192.168.1.16     worker1   <none>           <none>
kube-proxy-nnnl6                           1/1     Running   0          3h38m   192.168.1.17     worker2   <none>           <none>
kube-proxy-wrgcs                           1/1     Running   0          3h50m   192.168.1.15     control   <none>           <none>
kube-scheduler-control                     1/1     Running   0          3h50m   192.168.1.15     control   <none>           <none>
metrics-server-5846d89996-lf78z            1/1     Running   0          36s     10.244.235.129   worker1   <none>           <none>

5 動作確認

(1) 初期状態の確認
ノードのリソース使用状況を確認します。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   159m         3%       1949Mi          55%
worker1   46m          1%       1029Mi          29%
worker2   46m          1%       972Mi           27%

(2) CPU0 に負荷をかける
CPU0 に対して、CPU 使用率を高める負荷(CPU 負荷)をかけます。

[root@control ~]# stress-ng -c 1 --taskset 0 -q &
[1] 75755

CPU使用率を確認すると、CPU0(PSR列が 0)の CPU使用率が96%になっていることが分かります。

[root@control ~]# ps -C stress-ng,stress-ng-cpu -o comm,pid,ppid,%cpu,psr,wchan
COMMAND             PID    PPID %CPU PSR WCHAN
stress-ng         75755    3885  0.0   0 do_wait
stress-ng-cpu     75756   75755 96.0   0 -

control ノードの CPU 使用率を確認すると、28% に増加していることが分かります。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   1138m        28%      1989Mi          56%
worker1   54m          1%       1045Mi          29%
worker2   52m          1%       987Mi           28%

(3) CPU1 に負荷をかける
CPU1 に対して、CPU 使用率を高める負荷(CPU 負荷)をかけます。

[root@control ~]# stress-ng -c 1 --taskset 1 -q &
[2] 76990

CPU使用率を確認すると、CPU1(PSR列が 0)の CPU使用率が102%になっていることが分かります。

[root@control ~]# ps -C stress-ng,stress-ng-cpu -o comm,pid,ppid,%cpu,psr,wchan
COMMAND             PID    PPID %CPU PSR WCHAN
stress-ng         75755    3885  0.0   0 do_wait
stress-ng-cpu     75756   75755 98.9   0 -
stress-ng         76990    3885  0.0   1 do_wait
stress-ng-cpu     76991   76990  102   1 -

control ノードの CPU 使用率を確認すると、53% に増加していることが分かります。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   2137m        53%      2005Mi          56%
worker1   57m          1%       1045Mi          29%
worker2   63m          1%       987Mi           28%

(4) CPU2 に負荷をかける
CPU2 に対して、CPU 使用率を高める負荷(CPU 負荷)をかけます。

[root@control ~]# stress-ng -c 1 --taskset 2 -q &
[3] 77977

CPU使用率を確認すると、CPU2(PSR列が 0)の CPU使用率が102%になっていることが分かります。

[root@control ~]# ps -C stress-ng,stress-ng-cpu -o comm,pid,ppid,%cpu,psr,wchan
COMMAND             PID    PPID %CPU PSR WCHAN
stress-ng         75755    3885  0.0   0 do_wait
stress-ng-cpu     75756   75755 99.0   0 -
stress-ng         76990    3885  0.0   1 do_wait
stress-ng-cpu     76991   76990 99.6   1 -
stress-ng         77977    3885  0.0   2 do_wait
stress-ng-cpu     77978   77977  102   2 -

control ノードの CPU 使用率を確認すると、78% に増加していることが分かります。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   3140m        78%      2021Mi          57%
worker1   81m          2%       1044Mi          29%
worker2   75m          1%       987Mi           28%

(5) CPU3 に負荷をかける
CPU3 に対して、CPU 使用率を高める負荷(CPU 負荷)をかけます。

[root@control ~]# stress-ng -c 1 --taskset 3 -q &
[4] 78856

CPU 使用率を確認すると、すべての CPU コアがほぼ 100% の状態で動作していることが分かります。

[root@control ~]# ps -C stress-ng,stress-ng-cpu -o comm,pid,ppid,%cpu,psr,wchan
COMMAND             PID    PPID %CPU PSR WCHAN
stress-ng         75755    3885  0.0   0 do_wait
stress-ng-cpu     75756   75755 98.8   0 -
stress-ng         76990    3885  0.0   1 do_wait
stress-ng-cpu     76991   76990 99.0   1 -
stress-ng         77977    3885  0.0   2 do_wait
stress-ng-cpu     77978   77977 98.9   2 -
stress-ng         78856    3885  0.1   3 do_wait
stress-ng-cpu     78857   78856 90.5   3 -

control ノードの CPU 使用率を確認すると、99% に増加していることが分かります。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   3998m        99%      2037Mi          57%
worker1   92m          2%       1045Mi          29%
worker2   84m          2%       987Mi           28%

(6) あと始末
起動した stress-ng のプロセスを終了します。

[root@control ~]# jobs
[1]   実行中               stress-ng -c 1 --taskset 0 -q &
[2]   実行中               stress-ng -c 1 --taskset 1 -q &
[3]-  実行中               stress-ng -c 1 --taskset 2 -q &
[4]+  実行中               stress-ng -c 1 --taskset 3 -q &
[root@control ~]# kill %1
[root@control ~]# kill %2
[root@control ~]# kill %3
[root@control ~]# kill %4

6 まとめ

kubectl top node で表示される CPU(cores) は、CPU 使用量をコア数換算で表した値です。1000m が CPU 1コア分に相当します。

本環境は 4 コア構成のため、CPU(cores) が 3998m の場合は、約 4 コアすべてを使用している状態を示します。

CPU(%) は、ノードが持つ CPU 全体に対する使用率を示します。4 コアすべてに負荷をかけた結果、CPU(%) は約 99% となり、ノード全体の CPU がほぼ使い切られていることを確認できました。

今回の実験では、stress-ng を使用して CPU 0~3 に対して段階的に負荷をかけ、その変化を kubectl top node で確認しました。負荷の増加に合わせて CPU 使用量(CPU(cores)、CPU(%))が増加したことから、Metrics Server が Kubelet からメトリクスを正常に取得できていることを確認できました。

また、負荷をかけた control ノードのみ CPU 使用率が上昇し、worker ノードの CPU 使用率には大きな変化がないことも確認できました。


Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍

HarborにHelmチャートを登録し、Kubernetesから利用する

1 はじめに

Helmは、Kubernetesアプリケーションのデプロイや管理を行うためのパッケージマネージャです。Helmチャートを利用することで、複数のKubernetesリソースをまとめて管理し、アプリケーションを効率よくデプロイできます。

HelmチャートはBitnamiなどの公開リポジトリから取得できますが、実際の運用では、組織内で利用するチャートをHarborなどのプライベートレジストリで一元管理するケースもあります。これにより、利用するチャートのバージョンを管理しやすくなるほか、インターネットへ接続できない環境でもチャートを利用できます。

本記事では、Bitnamiリポジトリから取得したnginxのHelmチャートをHarborへ登録し、そのチャートを利用してKubernetesクラスタへnginxをデプロイする手順を紹介します。

なお、本記事は、以下の記事で紹介している手順が完了していることを前提としています。
hana-shin.hatenablog.com
hana-shin.hatenablog.com

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@control ~]# kubectl version
Client Version: v1.36.2
Kustomize Version: v5.8.1
Server Version: v1.36.2

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

3.1 Harbor接続のための事前準備(CA証明書の登録)

Helmクライアントは Harbor に HTTPS で接続し、チャートの登録や取得を行います。その際、Helmクライアントは Harbor から送られてくるサーバ証明書を、CA証明書に含まれる公開鍵を用いて検証します。この検証に失敗すると、TLS証明書の検証エラーが発生し、Harborへ接続できません。

本記事の検証環境では、Harbor のサーバ証明書は自己署名したCA証明書によって署名されています。そのため、Helmクライアントがサーバ証明書を正しく検証できるよう、事前にそのCA証明書をシステム(今回の場合、コントロールノード)へ登録する必要があります。

コントロールノードには、HarborをHTTPS化した際に作成したCA証明書がすでに配置されています。まずは、そのCA証明書の保存場所を確認します。

[root@control ~]# find . -name ca.crt
./harbor-certs/ca.crt

CA証明書の作成方法は、以下の記事で説明していますので参考にしてください。
https://hana-shin.hatenablog.com/entry/2026/07/14/221554#7-%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E4%BD%9C%E6%88%90

HarborのCA証明書を、システムが信頼するCA証明書を配置するディレクトリにコピーします。

[root@control ~]# cp ./harbor-certs/ca.crt /etc/pki/ca-trust/source/anchors/

証明書が正しく配置されたことを確認します。

[root@control ~]# ls -l /etc/pki/ca-trust/source/anchors/
合計 4
-rw-r--r--. 1 root root 2033  7月 27 09:36 ca.crt

追加したCA証明書をHelmクライアントが利用できるようにするため、update-ca-trust を実行します。このコマンドは、システム標準のCA証明書と /etc/pki/ca-trust/source/anchors/ に配置したCA証明書を入力として受け取り、アプリケーションが利用する信頼ストアを /etc/pki/ca-trust/extracted/pem/ 配下に生成します。HelmやcurlなどのHTTPS通信を行うアプリケーションは、この信頼ストアを参照してサーバ証明書を検証します。

[root@control ~]# update-ca-trust
[root@control ~]#

tls-ca-bundle.pem は、システム標準のCA証明書と追加したCA証明書をまとめたCA証明書バンドルです。HelmやcurlなどのHTTPS通信を行うアプリケーションは、このCA証明書バンドルを参照してサーバ証明書を検証します。

[root@control ~]# ls -l /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem
-r--r--r--. 1 root root 225804  7月 30 13:51 /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem

ちなみに、CA証明書バンドルには147枚のCA証明書が連結されています。BEGIN CERTIFICATE の数を数えることで、含まれているCA証明書の枚数を確認できます。

[root@control anchors]# grep "BEGIN CERTIFICATE" /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem |less
[root@control anchors]# grep "BEGIN CERTIFICATE" /etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem |wc -l
147

3.2 外部リポジトリからチャートのダウンロード

nginxのチャートが格納されているリポジトリをbitnamiという名前で登録します。

[root@control ~]# helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" has been added to your repositories

登録したリポジトリの一覧を確認します。bitnami という名前のリポジトリが登録されていることが分かります。

[root@control ~]# helm repo list
NAME            URL
metallb         https://metallb.github.io/metallb
ingress-nginx   https://kubernetes.github.io/ingress-nginx
harbor          https://helm.goharbor.io
bitnami         https://charts.bitnami.com/bitnami

ローカルにキャッシュされているチャート情報を最新の状態に更新します。この操作を行わないと、最新のチャート情報がローカルに反映されず、最新バージョンの検索や取得ができない場合があるため、事前に実行します。

[root@control ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "harbor" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
...Successfully got an update from the "bitnami" chart repository
Update Complete. ?Happy Helming!?

bitnamiリポジトリで公開されている nginx チャートのバージョン一覧を確認します。

[root@control ~]# helm search repo bitnami/nginx --versions
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION
bitnami/nginx                           25.0.15         1.31.3          NGINX Open Source is a web server that can be a...
bitnami/nginx                           25.0.14         1.31.3          NGINX Open Source is a web server that can be a...
bitnami/nginx                           25.0.13         1.31.2          NGINX Open Source is a web server that can be a...
-snip-

bitnamiリポジトリから、バージョン 25.0.15 のnginxチャートをカレントディレクトリにダウンロードします。

[root@control ~]# helm pull bitnami/nginx --version 25.0.15

ダウンロードしたチャートを確認します。

[root@control ~]# ls -l nginx-25.0.15.tgz
-rw-r--r--. 1 root root 62091  7月 27 08:56 nginx-25.0.15.tgz

4 Harborへのチャート登録

HarborのOCIレジストリに対してhelmクライアントから認証を行い、ログインします。

[root@control ~]# helm registry login harbor.home.lab
Username: admin
Password:
Login Succeeded

ダウンロードしたチャートを、Harborの library プロジェクト内のOCIリポジトリへ push します。

[root@control ~]# helm push nginx-25.0.15.tgz oci://harbor.home.lab/library
Pushed: harbor.home.lab/library/nginx:25.0.15
Digest: sha256:d2156f93e0e6e9b3d2f4975117aef39b1a874f26192ed391dc8c0aefac1437ec

HarborのWeb画面を確認すると、pushしたチャートが登録されていることが確認できます。

詳細を確認すると以下のようになります。

5 Harborに登録したチャートのインストール

Harborに登録したチャートをHelmでインストールします。STATUS が deployed となっていることから、Helmリリースが正常に作成されたことが分かります。

[root@control ~]# helm install my-nginx oci://harbor.home.lab/library/nginx --version 25.0.15
Pulled: harbor.home.lab/library/nginx:25.0.15
Digest: sha256:d2156f93e0e6e9b3d2f4975117aef39b1a874f26192ed391dc8c0aefac1437ec
NAME: my-nginx
LAST DEPLOYED: Mon Jul 27 11:42:59 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 25.0.15
APP VERSION: 1.31.3

? WARNING: Since August 28th, 2025, only a limited subset of images/charts are available for free.
    Subscribe to Bitnami Secure Images to receive continued support and security updates.
    More info at https://bitnami.com and https://github.com/bitnami/containers/issues/83267

** Please be patient while the chart is being deployed **
NGINX can be accessed through the following DNS name from within your cluster:

    my-nginx.default.svc.cluster.local (port 80)

To access NGINX from outside the cluster, follow the steps below:

1. Get the NGINX URL by running these commands:

  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        Watch the status with: 'kubectl get svc --namespace default -w my-nginx'

    export SERVICE_PORT=$(kubectl get --namespace default -o jsonpath="{.spec.ports[0].port}" services my-nginx)
    export SERVICE_IP=$(kubectl get svc --namespace default my-nginx -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    echo "http://${SERVICE_IP}:${SERVICE_PORT}"
WARNING: Rolling tag detected (bitnami/nginx:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/bitnami-secure-images/bitnami-secure-images/services/bsi-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: Rolling tag detected (bitnami/git:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/bitnami-secure-images/bitnami-secure-images/services/bsi-doc/apps-tutorials-understand-rolling-tags-containers-index.html
WARNING: Rolling tag detected (bitnami/nginx-exporter:latest), please note that it is strongly recommended to avoid using rolling tags in a production environment.
+info https://techdocs.broadcom.com/us/en/vmware-tanzu/bitnami-secure-images/bitnami-secure-images/services/bsi-doc/apps-tutorials-understand-rolling-tags-containers-index.html

WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
  - cloneStaticSiteFromGit.gitSync.resources
  - resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
[root@control ~]# ls

Helmでインストールしたリリースの一覧を確認します。my-nginx のSTATUSが deployed になっていることから、リリースが正常にデプロイされたことが分かります。

[root@control ~]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
my-nginx        default         1               2026-07-27 11:42:59.189915516 +0900 JST deployed        nginx-25.0.15   1.31.3

Podの状態を確認します。STATUSが Running、READYが 1/1 であることから、nginxのPodが正常に起動していることが分かります。

[root@control ~]# kubectl get pods
NAME                       READY   STATUS    RESTARTS   AGE
my-nginx-9cfd89585-tzlzr   1/1     Running   0          101s

Serviceの割り当て状況を確認します。TYPEが LoadBalancer、EXTERNAL-IP に 192.168.116.201 が割り当てられていることから、MetalLB によって LoadBalancer型Service に外部公開用のIPアドレスが割り当てられ、クラスタ外部から nginx にアクセスできる状態になっていることが分かります。

[root@control ~]# kubectl get svc my-nginx
NAME       TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)                      AGE
my-nginx   LoadBalancer   10.104.112.245   192.168.116.201   80:31346/TCP,443:30172/TCP   114s

6 あと始末

検証用にデプロイした nginx のリリースをアンインストールします。

[root@control ~]# helm uninstall my-nginx
release "my-nginx" uninstalled

Helmのリリース一覧を確認します。リリースが表示されていないことから、my-nginx のリリースが削除されたことが分かります。

[root@control ~]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION

Podの存在確認を行います。nginxのPodが削除されていることが確認できます。

[root@control ~]# kubectl get pods
No resources found in default namespace.

Serviceの割り当て状況を確認します。Serviceも正常に削除されていることが分かります。

[root@control ~]# kubectl get svc my-nginx
Error from server (NotFound): services "my-nginx" not found

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍

Trivy Operatorで、稼働中Podの脆弱性を調べる

1 はじめに

新規に脆弱性(CVE)が見つかった場合、運用する環境への影響を即座に確認し、必要であれば速やかにパッチ適用などの対応を行う必要があります。しかし、実際に運用している環境の中に該当の脆弱性が含まれているかどうかを即座に調べることは、容易ではありません。

この課題を解決するのが、Trivy Operatorです。Trivy Operatorは、ワークロード(DeploymentやDaemonSetなど)が作成または更新されると、そのワークロードで使用されるコンテナイメージを自動的にスキャンし、イメージに含まれる脆弱性を検出するツールです。

検出結果は VulnerabilityReport というKubernetesリソースとして保存されるため、kubectl コマンドを使用して、クラスタ内で利用されているコンテナイメージにどのような脆弱性が存在するかを確認できます。

本記事では、Trivy Operatorを導入し、実際に稼働中のPodが特定のCVEに該当するかどうかを検証します。

github.com


なお、本記事は、以下の記事で紹介している手順が完了していることを前提としています。
hana-shin.hatenablog.com
hana-shin.hatenablog.com

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.14                 |.15                |.16
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@control ~]# kubectl version
Client Version: v1.36.2
Kustomize Version: v5.8.1
Server Version: v1.36.2

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 事前準備

3.1 Harborのホスト名登録

すべてのノード(コントロールノード、ワーカーノード)の /etc/hosts に、Harbor(harbor.home.lab)のIPアドレスを登録します。これにより、各ノードがharbor.home.labというホスト名でHarborを参照できるようになります。

[root@control ~]# cat /etc/hosts
-snip-
192.168.1.10 control
192.168.1.11 worker1
192.168.1.12 worker2
192.168.1.200 harbor.home.lab

3.2 ワーカーノードへのCA証明書の配置(全てのワーカーノードで実施)

コントロールノードには、HarborをHTTPS化した際に作成したCA証明書がすでに配置されています。

[root@control ~]# find . -name ca.crt
./harbor-certs/ca.crt

CA証明書の作成方法は、以下の記事で説明していますので参考にしてください。
https://hana-shin.hatenablog.com/entry/2026/07/14/221554#7-%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E4%BD%9C%E6%88%90

全てのワーカーノードに、CA証明書を配置するためのディレクトリを作成します。CA証明書はワーカーノードのcontainerdがHarborへHTTPSで接続する際、Harborから送られてくるサーバー証明書の署名を検証するために使用します。今回のHarborは自己署名のCA証明書で署名されているため、そのCA証明書をワーカーノードへ配置する必要があります。

[root@worker1 ~]# mkdir -p /etc/containerd/certs.d/harbor.home.lab
[root@worker2 ~]# mkdir -p /etc/containerd/certs.d/harbor.home.lab

コントロールノードで作成したCA証明書を、全てのワーカーノードへコピーします。

[root@control ~]# scp /root/harbor-certs/ca.crt worker1:/etc/containerd/certs.d/harbor.home.lab/ca.crt
root@worker1's password:
ca.crt                                                                                                   100% 2033   267.8KB/s   00:00
[root@control ~]# scp /root/harbor-certs/ca.crt worker2:/etc/containerd/certs.d/harbor.home.lab/ca.crt
root@worker2's password:
ca.crt                                                                                                   100% 2033   969.9KB/s   00:00

全てのワーカーノードで、containerdがHarborへHTTPS接続する際に参照する設定ファイルhosts.tomlを作成します。caにはCA証明書のパスを配列形式で指定します。

[root@worker1 ~]# vi /etc/containerd/certs.d/harbor.home.lab/hosts.toml
[root@worker1 ~]# cat /etc/containerd/certs.d/harbor.home.lab/hosts.toml
server = "https://harbor.home.lab"

[host."https://harbor.home.lab"]
  ca = ["/etc/containerd/certs.d/harbor.home.lab/ca.crt"]

3.3 containerdのレジストリ証明書参照設定(全てのワーカーノードで実施)

全てのワーカーノードでcontainerdの設定ファイル(config.toml)を編集します。設定ファイルはデフォルトでは存在しないため、以下のように実行して作成します。

[root@worker1 ~]# containerd config default > /etc/containerd/config.toml

編集前に、変更差分を確認できるよう設定ファイルのバックアップを取得します。

[root@worker1 ~]# cp /etc/containerd/config.toml /etc/containerd/config.toml.org

containerdがCA証明書を格納したディレクトリを参照するようconfig_pathを設定します。

[root@worker1 ~]# vi /etc/containerd/config.toml

変更内容を確認します。config_pathにCA証明書を格納したディレクトリを指定しています。なお、SystemdCgroup = trueはKubernetes構築時に設定済みの内容です。

[root@worker1 ~]# diff -Nur /etc/containerd/config.toml.org /etc/containerd/config.toml
--- /etc/containerd/config.toml.org     2026-07-14 09:12:25.609474288 +0900
+++ /etc/containerd/config.toml 2026-07-22 19:59:21.408811282 +0900
@@ -51,7 +51,7 @@
       sandbox = 'registry.k8s.io/pause:3.10.1'

     [plugins.'io.containerd.cri.v1.images'.registry]
-      config_path = ''
+      config_path = '/etc/containerd/certs.d'

     [plugins.'io.containerd.cri.v1.images'.image_decryption]
       key_model = 'node'
@@ -106,7 +106,7 @@
             NoNewKeyring = false
             Root = ''
             ShimCgroup = ''
-            SystemdCgroup = false
+            SystemdCgroup = true

     [plugins.'io.containerd.cri.v1.runtime'.cni]
       bin_dir = ''

containerd.io



Harbor用の証明書ファイルが正しく配置されていることを確認します。

[root@worker1 ~]# ls -l /etc/containerd/certs.d/harbor.home.lab/
合計 8
-rw-r--r--. 1 root root 2033  7月 22 19:51 ca.crt
-rw-r--r--. 1 root root  127  7月 22 20:32 hosts.toml

設定を反映するため、containerdを再起動します。

[root@worker1 ~]# systemctl restart containerd

3.4 Harborのデプロイ防止ポリシーの無効化

Harborには、指定した重大度以上の脆弱性を含むイメージのpullを禁止するポリシー(Prevent vulnerable images from running)があります。今回は脆弱性を含むイメージをTrivy Operatorでスキャンすることが目的であるため、このポリシーを一時的に無効化します。

3.5 Harborの名前解決設定

Trivy Operatorが作成するスキャン用Podは、イメージをpullする際にHarbor(harbor.home.lab)へアクセスします。そのため、harbor.home.labをIPアドレスに変換するための設定を追加します。

CoreDNSの設定はConfigMapとして管理されているため、kubectl editコマンドで編集します。

[root@control ~]# kubectl edit configmap coredns -n kube-system

CoreDNSの設定ファイルにhosts を追加します

        prometheus :9153
        hosts {
           192.168.1.200 harbor.home.lab
           fallthrough
        }
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }

coredns.io



設定を反映するため、CoreDNSのDeploymentを再起動します。

[root@control ~]# kubectl rollout restart deployment coredns -n kube-system

一時的にBusyBoxコンテナを起動し、Harbor(harbor.home.lab)の名前解決が正常に行えることを確認します

[root@control ~]# kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup harbor.home.lab
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      harbor.home.lab
Address 1: 192.168.1.200 harbor.home.lab
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
Session ended, resume using 'kubectl attach dns-test -c dns-test -n default -i -t' command
pod "dns-test" deleted from default namespace

4 Trivy Operatorのインストール手順

Trivy Operatorが含まれるリポジトリをaquaという名前で登録します。

[root@control ~]# helm repo add aqua https://aquasecurity.github.io/helm-charts/
"aqua" has been added to your repositories

登録済みリポジトリを確認します。

[root@control ~]# helm repo list
NAME            URL
metallb         https://metallb.github.io/metallb
ingress-nginx   https://kubernetes.github.io/ingress-nginx
harbor          https://helm.goharbor.io
aqua            https://aquasecurity.github.io/helm-charts/

Helmはリポジトリ情報をローカルにキャッシュしており、古いキャッシュのままでは最新バージョンのチャートがインストールできない場合があるため、helm repo updateコマンドを実行して最新のチャート情報を取得します。

[root@control ~]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "metallb" chart repository
...Successfully got an update from the "aqua" chart repository
...Successfully got an update from the "ingress-nginx" chart repository
...Successfully got an update from the "harbor" chart repository
Update Complete. ?Happy Helming!?

Trivy Operator 用の namespace を作成します。

[root@control ~]# kubectl create namespace trivy-system
namespace/trivy-system created

Trivy Operator用の設定ファイルを保存するディレクトリを作成します。

[root@control ~]# mkdir -p ~/trivy-operator-install

trivy-operatorの設定ファイル(values.yaml)を作成します。sslCertDirには、CA証明書を格納したディレクトリを指定します。Trivy Operatorは、このディレクトリをhostPathボリュームとしてスキャン用Podにマウントし、プライベートレジストリとのHTTPS通信で使用するCA証明書として利用します。また、ignoreUnfixed: falseを設定することで、修正版が提供されていない脆弱性についても除外せず、検出結果に含めるようになります。

[root@control ~]# vi ~/trivy-operator-install/values.yaml
[root@control ~]# cat ~/trivy-operator-install/values.yaml
trivy:
  ignoreUnfixed: false
  sslCertDir: "/etc/containerd/certs.d/harbor.home.lab"
  useBuiltinCache: false

operator:
  scanJobsConcurrentLimit: 1

scanJobsConcurrentLimit は、同時に実行できるスキャンジョブの最大数を指定するパラメータです。今回の検証環境では、当初このパラメータを設定していなかったため、複数のスキャンジョブが同時に実行され、スキャンが完了しない状況が発生しました。そこで、この値を 1 に設定したところ、スキャンが正常に完了しました。環境によっては、同時実行数を増やしても問題なく動作するため、利用するクラスタのリソース状況に応じて適切な値を設定するとよいでしょう。

作成した values.yaml を指定して、Trivy Operator をインストールします。

[root@control ~]# helm install trivy-operator aqua/trivy-operator --namespace trivy-system -f ~/trivy-operator-install/values.yaml
NAME: trivy-operator
LAST DEPLOYED: Sun Jul 26 08:49:21 2026
NAMESPACE: trivy-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
You have installed Trivy Operator in the trivy-system namespace.
It is configured to discover Kubernetes workloads and resources in
all namespace(s).

Inspect created VulnerabilityReports by:

    kubectl get vulnerabilityreports --all-namespaces -o wide

Inspect created ConfigAuditReports by:

    kubectl get configauditreports --all-namespaces -o wide

Inspect the work log of trivy-operator by:

    kubectl logs -n trivy-system deployment/trivy-operator

Helm リリース一覧を確認します。

[root@control ~]# helm list -n trivy-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
trivy-operator  trivy-system    1               2026-07-26 08:49:21.026057855 +0900 JST deployed        trivy-operator-0.34.0   0.32.0

インストール直後は、クラスタ内で稼働している全てのワークロードを対象に、スキャンが一斉に開始されます。Trivy Operatorはワークロード1つに対してスキャン用Pod(scan-vulnerabilityreport-*)を1つ生成し、スキャンが完了すると自動的に削除します。スキャン対象のワークロードが複数存在する場合、この処理がワークロードの数だけ繰り返されるため、インストール直後はscan-vulnerabilityreport-*という名前のPodの生成と終了が続く状態になります。

[root@control ~]# kubectl get pods -n trivy-system
NAME                                        READY   STATUS    RESTARTS   AGE
node-collector-855455d7f8-2bflx             0/1     Pending   0          58s
scan-vulnerabilityreport-7b99c4b95d-wts2h   1/1     Running   0          60s
trivy-operator-5f54f465b7-nbrwh             1/1     Running   0          62s

以下のコマンドを実行すると、スキャン結果のレポートが順次作成されていく様子が確認できます。最終的にはクラスタ内の全てのPodに対するレポートが表示されますが、全てのレポートが出揃うのを待たずに次の手順に進んでも問題ありません。

[root@control ~]# kubectl get vulnerabilityreports -A
NAMESPACE     NAME                                        REPOSITORY                      TAG       SCANNER   AGE
harbor        statefulset-harbor-database-database        goharbor/harbor-db              v2.15.1   Trivy     36s
harbor        statefulset-harbor-trivy-trivy              goharbor/trivy-adapter-photon   v2.15.1   Trivy     91s
kube-system   pod-kube-scheduler-control-kube-scheduler   kube-scheduler                  v1.36.2   Trivy     41s

5 動作確認

ここでは、Harborに登録したNginxのイメージを使ってPodを起動し、Trivy Operatorが脆弱性レポートを生成できることを確認します。

(1) Harbor認証用のSecretを作成
Nginxのイメージを登録したlibraryプロジェクトは、Privateに設定しているため、containerdがHarborからイメージをpullする際に認証が必要です。Harborの認証情報をSecretとして登録します。

[root@control ~]# kubectl create secret docker-registry harbor-cred \
  --docker-server=harbor.home.lab \
  --docker-username=admin \
  --docker-password=Harbor12345 \
  --docker-email=dummy@example.com \
  -n default
secret/harbor-cred created

Secretが作成されたことを確認します。

[root@control ~]# kubectl get secrets
NAME          TYPE                             DATA   AGE
harbor-cred   kubernetes.io/dockerconfigjson   1      13s

(2) Deploymentマニフェストの作成
Harborに登録したNginxのイメージ(nginx:1.30)を使用するDeploymentのマニフェストを作成します。imagePullSecretsに先ほど作成したSecretを指定することで、HarborのPrivateプロジェクトからイメージをpullできるようになります。

[root@control ~]# vi nginx-test.yaml
[root@control ~]# cat nginx-test.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-test
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-test
  template:
    metadata:
      labels:
        app: nginx-test
    spec:
      containers:
        - name: nginx
          image: harbor.home.lab/library/nginx:1.30
      imagePullSecrets:
        - name: harbor-cred

(3) Pod起動確認
マニフェストを適用してDeploymentを作成します。

[root@control ~]# kubectl apply -f nginx-test.yaml
deployment.apps/nginx-test created

Podが正常に起動していることを確認します。

[root@control ~]# kubectl get pods
NAME                          READY   STATUS    RESTARTS   AGE
nginx-test-654b84664c-dvhp7   1/1     Running   0          11s

(4) Trivy Operatorの動作確認
Trivy Operatorは、ワークロードで使用されているコンテナイメージを自動的にスキャンし、脆弱性レポートを生成します。まずは、Trivy Operatorが正常に起動していることを確認します。

[root@control ~]# kubectl get pods -n trivy-system
NAME                                        READY   STATUS     RESTARTS   AGE
node-collector-855455d7f8-2nknj             0/1     Pending    0          40s
scan-vulnerabilityreport-5769c7bbdb-psnqd   0/1     Init:0/1   0          6s
trivy-operator-5f54f465b7-nbrwh             1/1     Running    0          9m30s

(5) VulnerabilityReportの生成確認
スキャン結果は、VulnerabilityReportリソースとして保存されます。

[root@control ~]# kubectl get vulnerabilityreports
NAME                                     REPOSITORY      TAG    SCANNER   AGE
replicaset-nginx-test-654b84664c-nginx   library/nginx   1.30   Trivy     3m54s

他のPodのスキャン結果は、-Aオプションを指定することで確認できます。

[root@control ~]# kubectl get vulnerabilityreports -A
NAMESPACE            NAME                                                          REPOSITORY                       TAG       SCANNER   AGE
default              replicaset-nginx-test-654b84664c-nginx                        library/nginx                    1.30      Trivy     15m
harbor               replicaset-harbor-core-57b57bcdd5-core                        goharbor/harbor-core             v2.15.1   Trivy     11m
harbor               replicaset-harbor-jobservice-7cddc45866-jobservice            goharbor/harbor-jobservice       v2.15.1   Trivy     19m
harbor               replicaset-harbor-portal-5b6bc45d5c-portal                    goharbor/harbor-portal           v2.15.1   Trivy     18m
harbor               replicaset-harbor-registry-748df588c7-registry                goharbor/registry-photon         v2.15.1   Trivy     20m
harbor               statefulset-harbor-database-database                          goharbor/harbor-db               v2.15.1   Trivy     25m
harbor               statefulset-harbor-redis-redis                                goharbor/redis-photon            v2.15.1   Trivy     24m
harbor               statefulset-harbor-trivy-trivy                                goharbor/trivy-adapter-photon    v2.15.1   Trivy     26m
ingress-nginx        replicaset-ingress-nginx-controller-5cd9869bf8-controller     ingress-nginx/controller         v1.15.1   Trivy     14m
kube-system          daemonset-calico-node-calico-node                             calico/node                      v3.32.1   Trivy     10m
kube-system          daemonset-kube-proxy-kube-proxy                               kube-proxy                       v1.36.2   Trivy     8m47s
kube-system          pod-etcd-control-etcd                                         etcd                             3.6.8-0   Trivy     23m
kube-system          pod-kube-apiserver-control-kube-apiserver                     kube-apiserver                   v1.36.2   Trivy     24m
kube-system          pod-kube-controller-manager-control-kube-controller-manager   kube-controller-manager          v1.36.2   Trivy     21m
kube-system          pod-kube-scheduler-control-kube-scheduler                     kube-scheduler                   v1.36.2   Trivy     25m
kube-system          replicaset-7d57bc9844                                         calico/kube-controllers          v3.32.1   Trivy     21m
kube-system          replicaset-coredns-779fc6c7c4-coredns                         coredns/coredns                  v1.14.2   Trivy     15m
local-path-storage   replicaset-c64f4fcf9                                          rancher/local-path-provisioner   v0.0.36   Trivy     13m
metallb-system       replicaset-metallb-controller-bc9cbb54b-controller            metallb/controller               v0.16.1   Trivy     12m
trivy-system         replicaset-trivy-operator-5f54f465b7-trivy-operator           aquasec/trivy-operator           0.32.0    Trivy     24m

6 脆弱性の確認

Trivy Operatorによって生成されたVulnerabilityReportを使い、脆弱性の確認方法を説明します。特定のPodに絞った確認と、クラスタ全体を横断した確認の2つの観点で説明します。

6.1 特定のPodの脆弱性確認

まず、default namespaceに存在するVulnerabilityReportの一覧を確認します。

[root@control ~]# kubectl get vulnerabilityreports -n default
NAME                                     REPOSITORY      TAG    SCANNER   AGE
replicaset-nginx-test-654b84664c-nginx   library/nginx   1.30   Trivy     40m

(1) 特定のPodに含まれる脆弱性を表示する方法
nginx:1.30に含まれる全CVEを、CVE ID・重大度・対象パッケージ・インストール済みバージョン・修正済みバージョンの形式で表示します。fixedVersionが空のものは、現時点で修正版が提供されていないCVEです。

[root@control ~]# kubectl get vulnerabilityreport replicaset-nginx-test-654b84664c-nginx -n default -o json |   jq '.report.vulnerabilities[] | {id: .vulnerabilityID, severity: .severity, resource: .resource, installedVersion: .installedVersion, fixedVersion: .fixedVersion}'
{
  "id": "CVE-2011-3374",
  "severity": "LOW",
  "resource": "apt",
  "installedVersion": "3.0.3",
  "fixedVersion": ""
}
{
  "id": "TEMP-0841856-B18BAF",
  "severity": "LOW",
  "resource": "bash",
  "installedVersion": "5.2.37-2+b9",
  "fixedVersion": ""
}
-snip-

(2) CVE件数のサマリー表示
nginxに含まれる脆弱性の総数、修正版が提供されているCVE数、修正版が未提供のCVE数を表示します。nginx:1.30の場合、合計347件の脆弱性が検出されており、修正版が提供されているものは1件、残りの346件は現時点で修正未提供の状態です。

[root@control ~]# kubectl get vulnerabilityreport replicaset-nginx-test-654b84664c-nginx -n default -o json | \
  jq '{total: (.report.vulnerabilities | length), fixable: [.report.vulnerabilities[] | select(.fixedVersion != "")] | length, unfixable: [.report.vulnerabilities[] | select(.fixedVersion == "")] | length}'
{
  "total": 347,
  "fixable": 1,
  "unfixable": 346
}

(3) 修正版が存在するCVEのみ表示
修正版が提供されているCVEのみを絞り込んで表示します。対応可能な脆弱性を優先的に把握したい場合に使用します。

[root@control ~]# kubectl get vulnerabilityreport replicaset-nginx-test-654b84664c-nginx -n default -o json | \
  jq '.report.vulnerabilities[] | select(.fixedVersion != "") | {id: .vulnerabilityID, severity: .severity, resource: .resource, installedVersion: .installedVersion, fixedVersion: .fixedVersion}'
{
  "id": "CVE-2026-12912",
  "severity": "UNKNOWN",
  "resource": "libtiff6",
  "installedVersion": "4.7.0-3+deb13u2",
  "fixedVersion": "4.7.0-3+deb13u3"
}
[root@control ~]#

6.2 システム全体の脆弱性確認

(1) 全ワークロードの脆弱性サマリー表示
クラスタ内の全ワークロードについて、重大度別(Critical・High・Medium・Low・Unknown)のCVE件数を一覧表示します。どのコンポーネントにどの程度の脆弱性が存在するかを把握するのに役立ちます。

[root@control ~]# kubectl get vulnerabilityreports -A -o json | \
  jq '.items[] | {namespace: .metadata.namespace, name: .metadata.name, critical: .report.summary.criticalCount, high: .report.summary.highCount, medium: .report.summary.mediumCount, low: .report.summary.lowCount, unknown: .report.summary.unknownCount}'
{
  "namespace": "default",
  "name": "replicaset-nginx-test-654b84664c-nginx",
  "critical": 4,
  "high": 16,
  "medium": 35,
  "low": 111,
  "unknown": 181
}
{
  "namespace": "harbor",
  "name": "replicaset-harbor-core-57b57bcdd5-core",
  "critical": 6,
  "high": 46,
  "medium": 56,
  "low": 5,
  "unknown": 32
}
-snip-

(2) 特定のCVEに該当するワークロードを横断検索
新たなCVEが公表された際、そのCVEに該当するワークロードがクラスタ内のどこで稼働しているかを即座に特定できます。以下はCVE-2026-12912を例に検索した結果です。

[root@control ~]# kubectl get vulnerabilityreports -A -o json |   jq '.items[] | select(.report.vulnerabilities[]?.vulnerabilityID == "CVE-2026-12912") | {namespace: .metadata.namespace, name: .metadata.name}'
{
  "namespace": "default",
  "name": "replicaset-nginx-test-654b84664c-nginx"
}

Z 参考資料

今回の記事執筆にあたり参考にした図書は以下のものです。

参考情報

containerd.io

単行本

電子書籍

Trivyによるコンテナイメージの脆弱性スキャンとポリシー制御

1 はじめに

Harborには、コンテナイメージの脆弱性スキャンを行う Trivy(トリビー)が標準で組み込まれています。本記事では、Harbor に統合された Trivy を利用してコンテナイメージをスキャンする方法や、その基本的な使い方を説明します。

trivy.dev

なお、Harbor のインストール手順は、以下のページをご覧ください。
hana-shin.hatenablog.com

2 検証環境

2.1 ネットワーク構成

検証環境は、VMware Workstation Pro上に構築した3台の仮想マシンでKubernetesクラスタを構成しています。各仮想マシンはブリッジ接続により、PCと同じネットワーク(192.168.1.0/24)に接続されています。

一方、10.244.0.0/16 は、CalicoのCNIプラグインによってクラスタ内に作成されるPodネットワークです。各Podにはこのアドレス帯からIPアドレスが割り当てられ、異なるノード上のPod同士が相互に通信する際に使用されます。Calicoは各ノードに経路を設定することで、Pod間通信を透過的に実現しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
|      Pod      |    |      Pod      |   |      Pod      |
|       |       |    |       |       |   |       |       |
| 10.244.x.0/24 |    | 10.244.y.0/24 |   | 10.244.z.0/24 |
| ------------- |    | ------------- |   | ------------- |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|        VMware Workstation Pro(Bridged networking)    |
+--------------------------------------------------------+
                             |
                    +---------------+
                    |               |
                    |       PC      |
                    |               |
                    +---------------+

それぞれの役割は以下のとおりです。
1台をコントロールノード、2台をワーカーノードとして使用します。

ホスト名 名称 役割
control コントロールノード クラスタ(control、worker1、worker2)の状態を管理し、Pod をどのノードで実行するかを決定するノード
worker1 ワーカーノード Pod を実行するノード
worker2 ワーカーノード Pod を実行するノード

2.2 ソフトウェアのバージョン

各ノードのAlmaLinuxバージョンは以下のとおりです。

[root@control ~]# cat /etc/redhat-release
AlmaLinux release 10.2 (Lavender Lion)

各ノードのカーネルバージョンは以下のとおりです。

[root@control ~]# uname -r
6.12.0-211.7.3.el10_2.x86_64

Kubernetesのバージョンは以下のとおりです。

[root@control ~]# kubectl version
Client Version: v1.36.2
Kustomize Version: v5.8.1
Server Version: v1.36.2

2.3 ノードのリソース

各ノードには4GBのメモリを割り当てています。

[root@control ~]#  free -h
               total        used        free      shared  buff/cache   available
Mem:           3.6Gi       1.3Gi       1.0Gi       5.8Mi       1.5Gi       2.3Gi
Swap:             0B          0B          0B

各ノードは 4コアのCPU(4 vCPU) を搭載しています。

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

lscpuコマンドの詳しい使い方は、以下のページをご覧ください。
hana-shin.hatenablog.com

3 スキャナー情報の確認

Harbor では、脆弱性スキャンや SBOM 生成を担当する「スキャナー」の情報を確認できます。ここでは、現在登録されているスキャナーや、そのバージョン、対応している機能を確認します。確認するには、Harbor の管理画面で次の順にクリックします。

Administration → Interrogation Services → Scanners

Scanner セクションには、現在登録されているスキャナー名やベンダー、バージョンなどの情報が表示されます。以下の例では、スキャナーが Trivy、スキャナー開発元が Aqua Security、Trivy のバージョンが v0.70.0 であることが確認できます。

Scanner:
    Name:    Trivy
    Vendor:  Aqua Security
    Version: v0.70.0

Capabilities セクションには、Trivy で利用できる機能が表示されます。以下の例では 2 つの Type が登録されており、Trivy がこれら 2 つの機能に対応していることがわかります。

Type 概要
Vulnerability コンテナイメージに含まれるソフトウェアやライブラリに既知の脆弱性がないかを検査する機能です。
SBOM(Software Bill of Materials) ソフトウェアに含まれるパッケージやライブラリを一覧化した「ソフトウェア部品表(SBOM)」を生成する機能です。イメージの構成要素を可視化できるため、脆弱性が発見された際の影響範囲の特定やライセンス管理などに役立ちます。

4 コンテナイメージのスキャン

コンテナイメージのスキャンは、イメージ内に含まれるソフトウェアやライブラリを調査し、既知の脆弱性(CVE)が含まれていないかを確認する機能です。スキャンを実行すると、検出された脆弱性の件数や重大度、対応状況などを確認できます。

Harbor で手動スキャンを行うには、対象イメージのチェックボックスを選択し、「SCAN VULNERABILITY」ボタンをクリックします。

以下はスキャン実行中の画面です。「Vulnerabilities」列に「Scanning」と表示されており、コンテナイメージの脆弱性スキャンが実行中であることがわかります。

以下はスキャン完了後の画面です。スキャン結果の詳細を確認するため、赤枠内をクリックします。

以下はスキャン完了後の画面です。スキャン結果の詳細を確認するため、赤枠内のリンクをクリックします。

以下は、個々の脆弱性(CVE)の詳細画面の例です。

この例では、Potential Mitigations(対策)項目が空欄になっていることから、2026年7月18日時点では、この CVE に対する修正パッチや回避策が Trivy 側のデータベースに登録されていないことがわかります。アップストリームで対策が公開されると、この欄にパッチのバージョンやワークアラウンドなどが表示される場合があります。

5 データベースのアップデート

Trivy は脆弱性スキャンの際、脆弱性データベースを参照し、コンテナイメージに含まれるソフトウェアやライブラリに既知の脆弱性がないかを確認します。このデータベースは定期的に更新され、新たに公開された脆弱性情報が反映されます。本章では、Trivy が使用する脆弱性データベースの更新タイミングと、Harbor の画面上から更新状況を確認する方法を説明します。なお、検証環境の Harbor は UTC で動作しているため、本章に記載する時刻はすべて UTC です。日本時間(JST)に換算する場合は、9時間加えてください。

今回の検証環境では、Trivy の設定は以下のとおりで、自動更新が有効になっています。

env.SCANNER_TRIVY_SKIP_UPDATE: false

スキャン前の Properties セクションのデータベース更新日時は、以下のとおりです。

vulnerability-database-next-update-at: 7/19/26, 4:19 PM
vulnerability-database-updated-at: 7/18/26, 4:19 PM

ここで、まだ脆弱性スキャンを実行していない別の nginx コンテナイメージに対してスキャンを実行します。

スキャンを実行した結果、Properties セクションのデータベース更新日時が、以下のように変化しました。

harbor.scanner-adapter/vulnerability-database-next-update-at:7/21/26, 4:44 PM
harbor.scanner-adapter/vulnerability-database-updated-at:7/20/26, 4:44 PM

vulnerability-database-updated-at が 7/18 から 7/20 に更新されており、スキャンの実行にあわせて脆弱性データベースが最新の状態に更新されたことがわかります。あわせて vulnerability-database-next-update-at も翌日の同時刻(7/21, 4:44 PM)に更新されており、次回の自動更新予定が新たにスケジュールされていることが確認できます。

6 自動スキャン

自動スキャンは、プロジェクト(本記事では library)へコンテナイメージを push するたびに、これまで手動で「SCAN VULNERABILITY」ボタンをクリックして実行していた脆弱性スキャンを自動的に実行する機能です。

設定するには、Harbor の Web UI で以下の順にクリックします。

Projects → library → Configuration

「Vulnerability scanning」セクションにある「Automatically scan images on push」にチェックを入れると、自動スキャンが有効になります。

動作確認のため、Docker Hub から新しいバージョンの nginx イメージを取得します。

[root@control ~]# podman pull docker.io/library/nginx:1.28

イメージの一覧を確認します。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.30        f91c43d45520  4 days ago    165 MB
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

取得したイメージを Harbor に push できるように、Harbor 用のタグ(別名)を付けます。

[root@control ~]# podman tag docker.io/library/nginx:1.28 harbor.home.lab/library/nginx:1.28

再度イメージ一覧を確認します。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.30        f91c43d45520  4 days ago    165 MB
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

次に、Harbor にログインしているかどうかを確認します。以下の結果より、まだ Harbor にログインしていないことがわかります。

[root@control ~]# podman login --get-login harbor.home.lab
Error: not logged into harbor.home.lab

Harborにログインします。

[root@control ~]# podman login harbor.home.lab
Username: admin
Password:
Login Succeeded!

Harborへコンテナイメージをアップロードします。

[root@control ~]# podman push harbor.home.lab/library/nginx:1.28

コンテナイメージの Harbor へのアップロードが完了すると、自動的にイメージの脆弱性スキャンが実行されていることが確認できます。イメージ一覧の「Vulnerabilities」列にスキャン結果が表示されます。

7 脆弱性の深刻度によるイメージ取得制御

「Prevent vulnerable images from running」は、指定した深刻度以上の脆弱性を含むコンテナイメージの pull を拒否する機能です。これにより、深刻な脆弱性を含むコンテナイメージが本番環境などで誤って利用されることを防止できます。本章では、この機能を無効にした場合と有効にした場合の挙動を比較し、実際にイメージの pull がどのように制御されるかを確認します。

(1) 事前準備
現在のイメージ一覧を確認します。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.30        f91c43d45520  4 days ago    165 MB
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

検証のため、ローカルにある nginx:1.30 のイメージを一度削除しておきます。

[root@control ~]# podman rmi harbor.home.lab/library/nginx:1.30
Untagged: harbor.home.lab/library/nginx:1.30
Deleted: f91c43d45520959a835dfeb3d9e62ec52610b983c6c20ea409b2f212b2d38fb0

イメージ一覧から、nginx:1.30 が削除されたことを確認します。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

(2) デプロイ防止ポリシー無効の場合
まず、「Prevent vulnerable images from running」を無効にした状態で動作を確認します。

ポリシーが無効な状態で、nginx:1.30 を pull します。

[root@control ~]# podman pull harbor.home.lab/library/nginx:1.30
Trying to pull harbor.home.lab/library/nginx:1.30...
Getting image source signatures
Copying blob e226c5634adb done   |
Copying blob 22b1b3aae7d1 done   |
Copying blob 6307db16215e done   |
Copying blob d98b66041796 done   |
Copying blob 74da90576f8d done   |
Copying blob bb9460f4024e done   |
Copying blob a0e1f1b23e48 done   |
Copying config f91c43d455 done   |
Writing manifest to image destination
f91c43d45520959a835dfeb3d9e62ec52610b983c6c20ea409b2f212b2d38fb0

ポリシーが無効な状態では、深刻度の高い脆弱性が検出されているイメージであっても、問題なく pull できることが確認できます。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.30        f91c43d45520  4 days ago    165 MB
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

(3) デプロイ防止ポリシー有効の場合
続いて、「Prevent vulnerable images from running」を有効にし、しきい値を「High」に設定して動作を確認します。

まず、先ほど pull した nginx:1.30 を再度削除しておきます。

[root@control ~]# podman rmi harbor.home.lab/library/nginx:1.30
Untagged: harbor.home.lab/library/nginx:1.30
Deleted: f91c43d45520959a835dfeb3d9e62ec52610b983c6c20ea409b2f212b2d38fb0

イメージ一覧から、nginx:1.30 が削除されたことを確認します。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

Harbor のプロジェクト設定画面で、ポリシーを有効化し、しきい値を「High」に設定します。

この状態で、再度 nginx:1.30 を pull します。

[root@control ~]# podman pull harbor.home.lab/library/nginx:1.30
Trying to pull harbor.home.lab/library/nginx:1.30...
WARN[0000] Failed, retrying in 1s ... (1/3). Error: initializing source docker://harbor.home.lab/library/nginx:1.30: reading manifest 1.30 in harbor.home.lab/library/nginx: unknown: current image with 340 vulnerabilities cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "High" or higher from running.' To continue with pull, please contact your project administrator to exempt matched vulnerabilities through configuring the CVE allowlist.
WARN[0001] Failed, retrying in 1s ... (2/3). Error: initializing source docker://harbor.home.lab/library/nginx:1.30: reading manifest 1.30 in harbor.home.lab/library/nginx: unknown: current image with 340 vulnerabilities cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "High" or higher from running.' To continue with pull, please contact your project administrator to exempt matched vulnerabilities through configuring the CVE allowlist.
WARN[0002] Failed, retrying in 1s ... (3/3). Error: initializing source docker://harbor.home.lab/library/nginx:1.30: reading manifest 1.30 in harbor.home.lab/library/nginx: unknown: current image with 340 vulnerabilities cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "High" or higher from running.' To continue with pull, please contact your project administrator to exempt matched vulnerabilities through configuring the CVE allowlist.
Error: unable to copy from source docker://harbor.home.lab/library/nginx:1.30: initializing source docker://harbor.home.lab/library/nginx:1.30: reading manifest 1.30 in harbor.home.lab/library/nginx: unknown: current image with 340 vulnerabilities cannot be pulled due to configured policy in 'Prevent images with vulnerability severity of "High" or higher from running.' To continue with pull, please contact your project administrator to exempt matched vulnerabilities through configuring the CVE allowlist.

エラーメッセージの通り、nginx:1.30 には 340 件の脆弱性が検出されており、「High」以上の脆弱性を含むイメージの pull を禁止するポリシーによって pull が拒否されました。また、CVE allowlist を設定することで、特定の脆弱性を例外として許可できることも示されています。

イメージ一覧を確認すると、pull に失敗したため nginx:1.30 が追加されていないことがわかります。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB
harbor.home.lab/library/nginx  1.28        fda7399e8104  3 months ago  164 MB
docker.io/library/nginx        1.28        fda7399e8104  3 months ago  164 MB

Z 参考図書

今回の記事執筆にあたり参考にした図書は以下のものです。

単行本

電子書籍