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日

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 参考図書

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

単行本

電子書籍

Harborにコンテナイメージを登録し、Kubernetesから利用する

1 はじめに

本記事では、Docker Hub から取得した nginx イメージを Harbor に push して登録し、Harbor から pull できることを確認します。

goharbor.io

なお、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 事前準備

3.1 アクセス権をPublicからPrivateに変更

Harbor では、プロジェクトごとに公開範囲を設定できます。今回は組織内だけで利用するプライベートレジストリとして使用するため、プロジェクトのアクセス権を Private に変更します。Harbor を初期状態のまま利用すると、下図のように「Project registry」のチェックボックスが有効になっており、プロジェクトは Public に設定されています。この状態では認証なしで誰でもリポジトリ内のコンテナイメージを閲覧したり、pull したりできます。そのため、意図しないコンテナイメージの公開につながるおそれがあります。

「Project registry」のチェックを外し、「Save」をクリックしてアクセス権を Private に変更します。

3.2 名前解決の設定

Podman から Harbor にアクセスする際は、Harbor のホスト名を IP アドレスへ名前解決できる必要があります。今回は DNS を使用しない構成のため、コントロールノードの /etc/hosts に Harbor のホスト名と IP アドレスを登録します。

[root@control ~]# cat /etc/hosts
-snip-
192.168.1.14 control
192.168.1.15 worker1
192.168.1.16 worker2
192.168.1.200 harbor.home.lab

これにより、 harbor.home.lab というホスト名で Harbor へアクセスできるようになります。

3.3 CA証明書の登録

コントロールノードから Podman を使って Harbor へ HTTPS 接続する際、Podman は Harbor のサーバ証明書が信頼できる CA(認証局)によって署名されているかを確認します。今回の環境では、Harbor 構築時に作成したCA 証明書を使用しているため、ブラウザから Harbor へアクセスする際に PC に登録したものと同じ CA 証明書を、Podman を利用するコントロールノードにも登録します。

まず、Harbor 構築時に作成した CA 証明書の場所を確認します。

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

次に、Podman が Harbor 用の証明書を参照できるよう、証明書格納ディレクトリを作成します。

[root@control ~]# mkdir -p /etc/containers/certs.d/harbor.home.lab

作成したディレクトリへ CA 証明書をコピーします。

[root@control ~]# cp ./harbor-certs/ca.crt /etc/containers/certs.d/harbor.home.lab/

コピーした CA 証明書を確認します。これで Podman は CA 証明書( ca.crt )を使って Harbor のサーバ証明書を検証できるようになります。

[root@control ~]# ls -l /etc/containers/certs.d/harbor.home.lab/
合計 4
-rw-r--r--. 1 root root 2033  7月 15 20:31 ca.crt

3.4 ログイン・ログアウトの確認

プロジェクトを Private に設定すると、コンテナイメージの登録(push)や取得(pull)には認証が必要になります。まず、 podman login コマンドを使用して Harbor へログインします。

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

ログインが成功すると、Harbor へコンテナイメージを登録したり、Harbor からコンテナイメージを取得したりできるようになります。

作業終了後は、必要に応じて podman logout コマンドでログアウトします。

[root@control ~]# podman logout harbor.home.lab
Removed login credentials for harbor.home.lab

4 Harborへのコンテナイメージの登録

4.1 Docker Hubからコンテナイメージを取得

Harbor へコンテナイメージを登録するには、まず登録元となるコンテナイメージが必要です。
ここでは、公開レジストリである Docker Hub から nginx のコンテナイメージを取得し、それを Harbor へ登録します。

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

取得したコンテナイメージを確認します。Docker Hub から取得した nginx:1.29 と nginx:1.30 がローカルに保存されていることがわかります。

[root@control ~]# podman images
REPOSITORY               TAG         IMAGE ID      CREATED       SIZE
docker.io/library/nginx  1.30        9e3d0670b564  34 hours ago  165 MB
docker.io/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB

4.2 Harborへのコンテナイメージの登録

Harborへコンテナイメージを登録するには、コンテナイメージにHarborのリポジトリ名を含むタグを付ける必要があります。podman tagコマンドはコンテナイメージをコピーするコマンドではなく、同じコンテナイメージに別名(タグ)を付けるだけです。そのため、ディスク容量はほとんど増えません。

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

コンテナイメージ一覧を確認します。docker.io/library/nginxとharbor.home.lab/library/nginxの両方が表示されていますが、IMAGE IDが同じであることから、同じコンテナイメージに2つのタグが付いているだけであることが分かります。

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

Harborへコンテナイメージを登録(push)するには認証が必要です。まず、現在Harborへログインしているか確認します。

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

ログインしていない状態でpushすると、認証エラーとなり、コンテナイメージを登録できません。

[root@control ~]# podman push harbor.home.lab/library/nginx:1.29
Getting image source signatures
Error: trying to reuse blob sha256:79dd1f4c855cd061f687a994426634cf5f84c8ecdbc66c7a7d118e828dd93c99 at destination: checking whether a blob sha256:79dd1f4c855cd061f687a994426634cf5f84c8ecdbc66c7a7d118e828dd93c99 exists in harbor.home.lab/library/nginx: authentication required

認証が必要であることを確認できたので、Harborへログインします。

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

ログイン後、nginx:1.29をHarborへ登録します。podman pushは、ローカルにあるコンテナイメージを指定したコンテナレジストリへアップロードするコマンドです。

[root@control ~]# podman push harbor.home.lab/library/nginx:1.29
Getting image source signatures
Copying blob 991efe4bb4e9 skipped: already exists
Copying blob a012aa2cdb54 skipped: already exists
Copying blob 0c33e4fe6a57 skipped: already exists
Copying blob b2ffe9a59a92 skipped: already exists
Copying blob e5f856badc1e skipped: already exists
Copying blob ba748f52d4dd skipped: already exists
Copying blob 685f3dcc5d4b skipped: already exists
Copying config 5dfe511714 done   |
Writing manifest to image destination

同様に、nginx:1.30もHarborへ登録します。

[root@control ~]# podman push harbor.home.lab/library/nginx:1.30
Getting image source signatures
Copying blob 371cab25d082 done   |
Copying blob 2ef7d316a5b0 done   |
Copying blob cbcca75214e2 done   |
Copying blob f7f5b0fd77cf done   |
Copying blob b5a653c38d7a done   |
Copying blob 22b1b3aae7d1 skipped: already exists
Copying blob a5fb2310538e done   |
Copying config f91c43d455 done   |
Writing manifest to image destination

4.3 Harborに登録したコンテナイメージの確認

Harbor の Web 管理画面を開き、コンテナイメージが正しく登録されていることを確認します。


4.4 あと始末(ローカルイメージの削除)

後続の手順で「Harbor に登録したイメージを利用できるか」を確認するため、いったんローカルに残っているイメージを削除します。まず現在のイメージ一覧を確認します。

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

docker.io/library/nginx と harbor.home.lab/library/nginx は異なる名前ですが、同じ IMAGE ID を参照しています。そのため、harbor.home.lab/library/nginx だけを削除しても、docker.io/library/nginx が残っている限りイメージ本体は削除されません。イメージ本体を削除するには、同じ IMAGE ID を参照するすべてのタグを削除する必要があります。

まず、 nginx:1.29 を削除します。

[root@control ~]# podman rmi harbor.home.lab/library/nginx:1.29 docker.io/library/nginx:1.29
Untagged: harbor.home.lab/library/nginx:1.29
Untagged: docker.io/library/nginx:1.29
Deleted: 5dfe511714e1fa9a9d1074193a6cc7fa0feab5d680be166336817a5fb57f4cbd

同様に、 nginx:1.30 も削除します。

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

最後にイメージ一覧を確認します。何も表示されなければ、ローカルから nginx のコンテナイメージが完全に削除されたことが確認できます。

[root@control ~]# podman images
REPOSITORY  TAG         IMAGE ID    CREATED     SIZE

5 Harborからのコンテナイメージのダウンロード

前章で library プロジェクトを Private に変更したため、Harbor からイメージを pull するには事前にログインしておく必要があります。まず、現在 Harbor にログイン済みかどうかを確認します。admin と表示され、ログイン済みであることが確認できました。もしログインしていない場合、この後の pull は unauthorized エラーで失敗するため、その場合は先に podman login harbor.home.lab を実行しておきます。

[root@control ~]# podman login --get-login harbor.home.lab
admin

ログインが確認できたので、ローカルにイメージが存在しない状態から、Harbor を指定して nginx:1.29 を pull します。

[root@control ~]# podman pull harbor.home.lab/library/nginx:1.29
Trying to pull harbor.home.lab/library/nginx:1.29...
Getting image source signatures
Copying blob e5f856badc1e done   |
Copying blob ba748f52d4dd done   |
Copying blob 0c33e4fe6a57 done   |
Copying blob 991efe4bb4e9 done   |
Copying blob b2ffe9a59a92 done   |
Copying blob a012aa2cdb54 done   |
Copying blob 685f3dcc5d4b done   |
Copying config 5dfe511714 done   |
Writing manifest to image destination
5dfe511714e1fa9a9d1074193a6cc7fa0feab5d680be166336817a5fb57f4cbd

同様に、 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 74da90576f8d done   |
Copying blob d98b66041796 done   |
Copying blob 22b1b3aae7d1 done   |
Copying blob 6307db16215e done   |
Copying blob bb9460f4024e done   |
Copying blob a0e1f1b23e48 done   |
Copying config f91c43d455 done   |
Writing manifest to image destination
f91c43d45520959a835dfeb3d9e62ec52610b983c6c20ea409b2f212b2d38fb0

最後にイメージ一覧を確認します。IMAGE ID が、4 章で削除する前のイメージと完全に一致していることから、Harbor 経由でも元のイメージと同一のデータが取得できたことがわかります。

[root@control ~]# podman images
REPOSITORY                     TAG         IMAGE ID      CREATED       SIZE
harbor.home.lab/library/nginx  1.30        f91c43d45520  36 hours ago  165 MB
harbor.home.lab/library/nginx  1.29        5dfe511714e1  2 months ago  165 MB

Z 参考図書

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

単行本

電子書籍

Harborのインストール方法

1 Harborとは?

Harborは、コンテナイメージを保存・管理するためのコンテナレジストリです。Docker Hubのような公開レジストリとは異なり、組織内に専用のレジストリを構築できるため、本番環境で使用するコンテナイメージを安全に一元管理できます。また、イメージの脆弱性スキャン、アクセス制御、イメージ署名、レプリケーションなど、コンテナを安全に運用するための機能を標準で提供しています。

goharbor.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.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 事前準備

本検証ではCNI(Container Network Interface)にCalicoを使用しており、ノード間のPod通信にはIPIPカプセル化方式(tunl0インターフェース)を使用しています。firewalldを有効にした環境では、Calicoが使用するトンネルインターフェースやPodネットワークに対する通信がfirewalldによって許可されていない場合、Pod間通信が正常に行えず、Harborなどのアプリケーションが正常に動作しないことがあります。そのため、この後の手順(MetalLB、Harborのインストール)を正常に進めるため、事前に以下の設定を行います。3章の設定は全てのノード(コントロールノード、ワーカノード)で実施します。

(1) tunl0 の状態確認
tunl0が現在どのゾーンに属しているか確認します。no zoneと表示された場合、以降の設定が必要です。

[root@control ~]# firewall-cmd --get-zone-of-interface=tunl0
no zone

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

(2) trustedゾーンへの追加
firewalldのtrustedゾーンは、そのゾーンに属する通信を許可する、最も信頼度の高いゾーンです。今回は、Calicoが使用するトンネルインターフェース(tunl0)と、Podネットワーク(10.244.0.0/16)からの通信を許可するため、これらをtrustedゾーンへ追加します。

tunl0インターフェースをtrustedゾーンへ追加します。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-interface=tunl0
success

Podネットワーク(10.244.0.0/16)をtrustedゾーンへ追加します。この設定により、Calicoが構成するPodネットワークの通信がfirewalldによって遮断されることを防ぎます。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-source=10.244.0.0/16
success

クラスタを構成するノードが接続されているネットワーク(192.168.1.0/24)をtrustedゾーンへ追加します。

[root@control ~]# firewall-cmd --permanent --zone=trusted --add-source=192.168.1.0/24
success

永続設定を反映するため、firewalldの設定をリロードします。

[root@control ~]# firewall-cmd --reload
success

(3) 設定確認
tunl0がtrustedゾーンに追加されたことを確認します。

[root@control ~]# firewall-cmd --get-zone-of-interface=tunl0
trusted

trustedゾーンの設定内容を確認します。interfacesにtunl0 が登録されていれば、設定は正しく反映されています。

[root@control ~]# firewall-cmd --zone=trusted --list-all
trusted (active)
  target: ACCEPT
  ingress-priority: 0
  egress-priority: 0
  icmp-block-inversion: no
  interfaces: tunl0
  sources: 10.244.0.0/16 192.168.1.0/24
  services:
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

4 ストレージ(Local Path Provisioner)のインストール

Harborは、コンテナイメージやデータベースなどのデータを永続的に保存するために、PersistentVolumeClaim(PVC)を使用します。ここでは、PVCの要求に応じてPersistentVolume(PV)を動的に作成するLocal Path Provisionerをインストールします。Local Path Provisionerは、ノード上のローカルディスクを利用して、ストレージを提供します。

curl コマンドを使用して、Local Path Provisionerのマニフェストファイルをダウンロードします。

[root@control ~]# curl -LO https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.36/deploy/local-path-storage.yaml
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3852  100  3852    0     0   1046      0  0:00:03  0:00:03 --:--:--  1046

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


ダウンロードしたマニフェストのバックアップを作成します。

[root@control ~]# cp local-path-storage.yaml local-path-storage.yaml.org

テキストエディタを使って、StorageClassをクラスタのデフォルトのStorageClassとして使用するため、local-path-storage.yaml を編集します。

[root@control ~]# vi local-path-storage.yaml

diff コマンドを使用し、StorageClassをデフォルトとして指定するアノテーションが追加されたことを確認します。

[root@control ~]# diff -Nur local-path-storage.yaml.org local-path-storage.yaml
--- local-path-storage.yaml.org 2026-07-12 08:58:45.916523536 +0900
+++ local-path-storage.yaml     2026-07-12 09:00:47.781709321 +0900
@@ -116,6 +116,8 @@
 kind: StorageClass
 metadata:
   name: local-path
+  annotations:
+    storageclass.kubernetes.io/is-default-class: "true"
 provisioner: rancher.io/local-path
 volumeBindingMode: WaitForFirstConsumer
 reclaimPolicy: Delete

編集したマニフェストをKubernetesクラスタに適用し、Local Path Provisionerをインストールします。

[root@control ~]# kubectl apply -f local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
role.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
rolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created

インストールが完了したら、Local Path ProvisionerのPodが正常に起動しているか確認します。STATUS が Running になっていればOKです。

[root@control ~]# kubectl get pods -n local-path-storage -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
local-path-provisioner-6d76485965-xq6s2   1/1     Running   0          24s   10.244.189.66   worker2   <none>           <none>

最後に、local-path がデフォルトのStorageClassとして正しく認識されているかを確認します。

[root@control ~]# kubectl get storageclass
NAME                   PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  42s

5 MetalLBのインストール

オンプレミス(自宅・社内インフラなど)では、クラウド環境とは異なり、LoadBalancer型のServiceを作成しても外部IPアドレス(EXTERNAL-IP)が自動で割り当てられません。MetalLBは、オンプレミス環境でもLoadBalancer型Serviceに対して外部IPアドレスを割り当てることができるソフトウェアです。今回は、後ほどHarborへのアクセスに使うIngress Controller(ingress-nginx)を外部公開するためにMetalLBを導入します。

5.1 MetalLBのインストール

MetalLBのリポジトリを追加します。

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

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

最新のチャート(パッケージ)情報を取得するため、Helmリポジトリの情報を更新します。

[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
Update Complete. ?Happy Helming!?

Helmを使ってMetalLBをインストールします。--create-namespace を付与することで、専用のNamespace(metallb-system)の作成とインストールを同時に行います。STATUS: deployed となれば成功です。

[root@control ~]# helm install metallb metallb/metallb --namespace metallb-system --create-namespace
I0712 09:11:29.887926   36180 warnings.go:107] "Warning: unrecognized format \"cidr\""
NAME: metallb
LAST DEPLOYED: Sun Jul 12 09:11:29 2026
NAMESPACE: metallb-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
MetalLB is now running in the cluster.

Now you can configure it via its CRs. Please refer to the metallb official docs
on how to use the CRs.

helm list コマンドを使い、指定したNamespaceにMetalLBが正常にデプロイされている状態か確認します。

[root@control ~]# helm list -n metallb-system
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
metallb metallb-system  1               2026-07-12 09:11:29.313813192 +0900 JST deployed        metallb-0.16.1  v0.16.1

MetalLBを構成する各Podの状態を確認します。すべてのPodが Running になっていれば正常に動作しています。

[root@control ~]# kubectl get pods -n metallb-system
NAME                                             READY   STATUS    RESTARTS   AGE
metallb-controller-bc9cbb54b-xsts9               1/1     Running   0          2m26s
metallb-frr-k8s-fcdvp                            5/5     Running   0          2m26s
metallb-frr-k8s-jtg94                            5/5     Running   0          2m26s
metallb-frr-k8s-s282w                            5/5     Running   0          2m26s
metallb-frr-k8s-statuscleaner-75b695f48d-dvtnc   1/1     Running   0          2m26s
metallb-speaker-fs475                            1/1     Running   0          2m26s
metallb-speaker-w9p7t                            1/1     Running   0          2m26s
metallb-speaker-wh8fd                            1/1     Running   0          2m26s

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

[root@control ~]# kubectl get svc -n metallb-system
NAME                      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
frr-k8s-webhook-service   ClusterIP   10.96.250.168   <none>        443/TCP   4m12s
metallb-webhook-service   ClusterIP   10.111.129.74   <none>        443/TCP   4m12s

5.2 IPプールの設定

MetalLBがLoadBalancer型のServiceに対して、どのIPアドレスを割り当てるべきかを定義したマニフェストを作成します。 
- IPAddressPool:払い出すIPアドレスの範囲(今回は 192.168.1.200〜210)を指定します。
- L2Advertisement:定義したIPアドレスを、L2モード(ARP)を使ってネットワーク内に通知するための設定です。

[root@control ~]# vi metallb-pool.yaml
[root@control ~]# cat metallb-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: first-pool
  namespace: metallb-system
spec:
  addresses:
    - 192.168.1.200-192.168.1.210
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2advertisement
  namespace: metallb-system

作成したマニフェストファイルをクラスタに適用します。

[root@control ~]# kubectl apply -f metallb-pool.yaml
ipaddresspool.metallb.io/first-pool created
l2advertisement.metallb.io/l2advertisement created

設定したIPプール(IPAddressPool)が正しく認識され、指定したIPアドレス範囲が登録されているか確認します。

[root@control ~]# kubectl get ipaddresspool -n metallb-system
NAME         AUTO ASSIGN   AVOID BUGGY IPS   ADDRESSES
first-pool   true          false             ["192.168.1.200-192.168.1.210"]

同様に、L2モードの通知設定(L2Advertisement)が正しく登録されているか確認します。

[root@control ~]# kubectl get l2advertisement -n metallb-system
NAME              IPADDRESSPOOLS   IPADDRESSPOOL SELECTORS   INTERFACES
l2advertisement

5.3 動作確認

設定したIPプールから、実際にServiceへIPアドレスが自動で割り当てられるかを確認します。まずは動作確認用として、シンプルなNginxのDeploymentを作成(Podをデプロイ)します。

[root@control ~]# kubectl create deployment nginx-test --image=nginx
deployment.apps/nginx-test created

Deploymentの詳細は、以下のページをご覧ください。
hana-shin.hatenablog.com

作成したNginxのDeploymentを、LoadBalancer 型のServiceとして外部へ公開します。

[root@control ~]# kubectl expose deployment nginx-test --port=80 --type=LoadBalancer
service/nginx-test exposed

作成されたServiceのステータスを確認します。EXTERNAL-IP の欄に、先ほどプールとして指定した範囲内から 192.168.1.200 が自動的に割り当てられていることがわかります。

[root@control ~]# kubectl get svc nginx-test
NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
nginx-test   LoadBalancer   10.110.52.162   192.168.1.200   80:31347/TCP   5s

curl コマンドを使い、割り当てられた外部IPアドレス(192.168.1.200)へHTTPリクエストを送信してみます。Serviceを介して背後のNginx Podへと正しくルーティングされ、Nginxから正常な応答が返ってきていることが確認できます。

[root@control ~]# curl http://192.168.1.200
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, nginx is successfully installed and working.
Further configuration is required for the web server, reverse proxy,
API gateway, load balancer, content cache, or other features.</p>

<p>For online documentation and support please refer to
<a href="https://nginx.org/">nginx.org</a>.<br/>
To engage with the community please visit
<a href="https://community.nginx.org/">community.nginx.org</a>.<br/>
For enterprise grade support, professional services, additional
security features and capabilities please refer to
<a href="https://f5.com/nginx">f5.com/nginx</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

動作確認が完了したため、テスト用に作成したServiceとDeploymentを削除して環境を元の状態に戻しておきます。

[root@control ~]# kubectl delete svc nginx-test
service "nginx-test" deleted from default namespace
[root@control ~]# kubectl delete deployment nginx-test
deployment.apps "nginx-test" deleted from default namespace

6 イングレスコントローラのインストール

ここでは、ingress-nginxというIngress Controllerをインストールします。
Ingressは、HTTP/HTTPSリクエストをホスト名やURLパスに応じてServiceへルーティングするためのルールを定義するオブジェクトです。しかし、Ingressを定義しただけではリクエストはルーティングされません。Ingressで定義したルールを読み取り、リバースプロキシとして動作し、リクエストを適切なServiceへ振り分ける役割を担うのがIngress Controllerです。

6.1 ingress-nginxのインストール

MetalLBのプールから払い出される、Ingress用のIP(例えば192.168.1.200)に対してホスト名を紐付けます。

  • 192.168.1.200 harbor.home.lab

ingress-nginxのHelmリポジトリを追加します。

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

Helmリポジトリの情報を最新の状態に更新します。

[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 "ingress-nginx" chart repository
Update Complete. ?Happy Helming!?

ingress-nginxをインストールします。--set controller.service.type=LoadBalancerを指定することで、ingress-nginxのServiceがLoadBalancer型で作成され、MetalLBによって外部IPアドレスが自動的に割り当てられます。

[root@control ~]# helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx \
  --create-namespace \
  --set controller.service.type=LoadBalancer
NAME: ingress-nginx
LAST DEPLOYED: Sun Jul 12 10:38:10 2026
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the load balancer IP to be available.
You can watch the status by running 'kubectl get service --namespace ingress-nginx ingress-nginx-controller --output wide --watch'

An example Ingress that makes use of the controller:
  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: example
    namespace: foo
  spec:
    ingressClassName: nginx
    rules:
      - host: www.example.com
        http:
          paths:
            - pathType: Prefix
              backend:
                service:
                  name: exampleService
                  port:
                    number: 80
              path: /
    # This section is only required if TLS is to be enabled for the Ingress
    tls:
      - hosts:
        - www.example.com
        secretName: example-tls

If TLS is enabled for the Ingress, a Secret containing the certificate and key must also be provided:

  apiVersion: v1
  kind: Secret
  metadata:
    name: example-tls
    namespace: foo
  data:
    tls.crt: <base64 encoded cert>
    tls.key: <base64 encoded key>
  type: kubernetes.io/tls

インストール後、Podの状態を確認します。

[root@control ~]# kubectl get pods -n ingress-nginx
NAME                                        READY   STATUS    RESTARTS   AGE
ingress-nginx-controller-5cd9869bf8-m584m   1/1     Running   0          49s

インストール後、Serviceの状態を確認します。ServiceのEXTERNAL-IPにMetalLBのプール範囲内(192.168.1.200〜192.168.1.210)のIPアドレスが割り当てられていれば成功です。

[root@control ~]# kubectl get svc -n ingress-nginx
NAME                                 TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)                      AGE
ingress-nginx-controller             LoadBalancer   10.97.221.138   192.168.1.200   80:32423/TCP,443:32058/TCP   55s
ingress-nginx-controller-admission   ClusterIP      10.98.231.61    <none>          443/TCP                      55s

7 証明書の作成

ここでは、公開認証局(Root CA)の代わりに、コントロールノード上でプライベートCAを構築します。具体的には、作業用ディレクトリを作成し、CAで使用する秘密鍵とCA証明書を作成します。続いて、作成したプライベートCAを使用してHarborのサーバ証明書に署名します。作成する証明書は以下のとおりです。

  • PCへインポートするCA証明書
  • HarborのHTTPS通信で使用するサーバ証明書

7.1 プライベートCAの証明書の作成

作業用ディレクトリを作成し、cdコマンドでharbor-certsディレクトリへ移動します。

[root@control ~]# mkdir -p ~/harbor-certs 
[root@control ~]# cd harbor-certs/

プライベートCAで使用する秘密鍵を作成します。

[root@control test]# openssl genpkey -algorithm RSA -out ca.key -pkeyopt rsa_keygen_bits:4096
[root@control harbor-certs]#

作成した秘密鍵(ca.key)が正常に生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 4
-rw-------. 1 root root 3268  7月 14 10:49 ca.key

プライベートCAのCA証明書(ca.crt)を作成します。このCA証明書は、Harborのサーバ証明書の検証に使用します。なお、検証環境のため、有効期限は10年(3650日)に設定しています。

[root@control harbor-certs]# openssl req -x509 -new -noenc -key ca.key -sha256 -days 3650 -out ca.crt -subj "/C=JP/ST=Tokyo/L=Machida/O=HomeLab/OU=CA/CN=HomeLab Root CA"

作成したCA証明書(ca.crt)と秘密鍵(ca.key)が正常に生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 8
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key

7.2 harborのサーバ証明書の作成

HarborとHTTPS通信を行うため、harbor.home.lab用のサーバー証明書を作成します。

まず、Harborで使用する4096ビットRSA秘密鍵を生成します。

[root@control harbor-certs]# openssl genpkey -algorithm RSA -out harbor.home.lab.key -pkeyopt rsa_keygen_bits:4096

作成したサーバー秘密鍵が生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 12
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

続いて、秘密鍵を使用してCSR(Certificate Signing Request:証明書署名要求)を作成します。CSRには、証明書へ埋め込むサーバー名などの情報が含まれます。

[root@control harbor-certs]# openssl req -new -key harbor.home.lab.key \
  -out harbor.home.lab.csr \
  -subj "/C=JP/ST=Tokyo/L=Machida/O=HomeLab/OU=Harbor/CN=harbor.home.lab"

CSR(harbor.home.lab.csr)とサーバー秘密鍵(harbor.home.lab.key)が生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 16
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-r--r--. 1 root root 1704  7月 14 10:51 harbor.home.lab.csr
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

サーバー証明書へSAN(Subject Alternative Name)を設定するための設定ファイルを作成します。Webブラウザでは、証明書のSANに接続先のホスト名やIPアドレスが登録されていることが必須となっています。

[root@control harbor-certs]# vi harbor.home.lab.ext
[root@control harbor-certs]# cat harbor.home.lab.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names

[alt_names]
DNS.1 = harbor.home.lab
IP.1 = 192.168.1.200

作成したCSRをプライベートCAの秘密鍵で署名し、Harborのサーバー証明書を発行します。検証環境のため、有効期限は1年(365日)に設定しています。

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

サーバー証明書(harbor.home.lab.crt)、CSR、秘密鍵などのファイルが生成されていることを確認します。

[root@control harbor-certs]# ls -l
合計 28
-rw-r--r--. 1 root root 2033  7月 14 10:50 ca.crt
-rw-------. 1 root root 3268  7月 14 10:49 ca.key
-rw-r--r--. 1 root root   41  7月 14 10:52 ca.srl
-rw-r--r--. 1 root root 2126  7月 14 10:52 harbor.home.lab.crt
-rw-r--r--. 1 root root 1704  7月 14 10:51 harbor.home.lab.csr
-rw-r--r--. 1 root root  257  7月 14 10:52 harbor.home.lab.ext
-rw-------. 1 root root 3272  7月 14 10:51 harbor.home.lab.key

最後に、サーバー証明書へSANが正しく設定されていることを確認します。

[root@control harbor-certs]# openssl x509 -in harbor.home.lab.crt -text -noout | grep -A2 "Subject Alternative Name"
            X509v3 Subject Alternative Name:
                DNS:harbor.home.lab, IP Address:192.168.1.200
            X509v3 Subject Key Identifier:

7.3 Kubernetes Secretへの登録

harabor Namespaceを作成します。

[root@control harbor-certs]# kubectl create namespace harbor
namespace/harbor created

HarborのIngressからTLS証明書を利用できるよう、作成したサーバー証明書と秘密鍵をKubernetes Secretとして登録します。

[root@control harbor-certs]# kubectl create secret tls harbor-tls \
  --cert=harbor.home.lab.crt \
  --key=harbor.home.lab.key \
  -n harbor
secret/harbor-tls created
[root@control harbor-certs]#

ConfigMap/Secretの詳細は、以下のページをご覧ください。
hana-shin.hatenablog.com

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

[root@control harbor-certs]# kubectl get secret harbor-tls -n harbor
NAME         TYPE                DATA   AGE
harbor-tls   kubernetes.io/tls   2      30s

8 PC側の作業

PCのブラウザから、コントロールノード上で稼働するHarborへHTTPSでアクセスします。このとき、ブラウザはHarborから送られてくるサーバ証明書を検証します。サーバ証明書の検証には、CA証明書に含まれる公開鍵が使用されます。そのため、事前にCA証明書をPCへインポートしておく必要があります。ここでは、CA証明書をWindowsへインポートする手順を説明します。

8.1 hostsファイルの編集

PCからHarborへホスト名でアクセスできるよう、Windowsのhostsファイル(C:\Windows\System32\drivers\etc\hosts)に以下の内容を追記します。

192.168.1.200  harbor.home.lab

8.2 CA証明書のインポート

コントロールノードで作成したCA証明書(ca.crt)をPCへ転送します。次に、転送したCA証明書をWindowsの証明書ストア(信頼されたルート証明機関)へインポートします。ChromeやMicrosoft EdgeはWindowsの証明書ストアを利用するため、この設定を行うことで、ブラウザからHarborへHTTPSでアクセスすることができるようになります。

まず、[Windows]キー + [R] を押して 「ファイル名を指定して実行」 を開きます。そして、「certmgr.msc」 と入力し、「OK」 をクリックします。

証明書マネージャー(Certmgr)が起動したら、「信頼されたルート証明機関」→「証明書」 を右クリックし、「すべてのタスク」→「インポート」 を選択します。その後、ウィザードに従って転送したCA証明書を選択し、Windowsへインポートします。

「次へ」をクリックする。

作成したCA証明書(ca.crt)を指定して「次へ」をクリックします。

「証明書をすべて次のストアに配置する(P)」から「信頼されたルート証明機関」を選択して「次へ」をクリックする。

「完了」をクリックする。

「はい」をクリックする

「正しくインポートされました」のポップアップ画面を確認する。

9 Harborのインストール

Harbor用のNamespaceを作成します。

[root@control ~]# kubectl create namespace harbor
namespace/harbor created

Harborのリポジトリを追加します。

[root@control ~]# helm repo add harbor https://helm.goharbor.io
"harbor" has been added to your repositories

リポジトリの情報を最新の状態に更新します。

[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 "ingress-nginx" chart repository
...Successfully got an update from the "harbor" chart repository
Update Complete. ?Happy Helming!?

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

[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

helm search repoコマンドを実行して、リポジトリに登録されているチャートを確認します。実行結果から、harborチャートが利用可能であることを確認できます。

[root@control ~]# helm search repo harbor
NAME            CHART VERSION   APP VERSION     DESCRIPTION
harbor/harbor   1.19.1          2.15.1          An open source trusted cloud native registry th...

Harborインストール用の作業ディレクトリを作成します。

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

HarborはHelmチャートを使用してインストールします。Helmでは、values.yamlに設定を記述することで、デフォルト設定を変更できます。ここでは、Harborへアクセスするホスト名やTLSの設定、永続ストレージの設定、管理者パスワードなどを指定するため、values.yamlを作成します。

[root@control ~]# vi ~/harbor-install/values.yaml
[root@control ~]# cat ~/harbor-install/values.yaml
expose:
  type: ingress
  tls:
    enabled: true
    certSource: secret
    secret:
      secretName: "harbor-tls"
  ingress:
    hosts:
      core: harbor.home.lab
    className: "nginx"

externalURL: https://harbor.home.lab

persistence:
  enabled: true
  persistentVolumeClaim:
    registry:
      storageClass: "local-path"
      size: 20Gi
    jobservice:
      jobLog:
        storageClass: "local-path"
        size: 1Gi
    database:
      storageClass: "local-path"
      size: 5Gi
    redis:
      storageClass: "local-path"
      size: 1Gi
    trivy:
      storageClass: "local-path"
      size: 5Gi

harborAdminPassword: "Harbor12345"

Harborをインストールします。-fオプションで指定したvalues.yamlの内容(Ingress・TLS・永続化ストレージの設定)が反映されます。

[root@control ~]# helm install harbor harbor/harbor -n harbor -f ~/harbor-install/values.yaml
NAME: harbor
LAST DEPLOYED: Sun Jul 12 11:24:12 2026
NAMESPACE: harbor
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://harbor.home.lab
For more details, please visit https://github.com/goharbor/harbor

Harborがインストールされていることを確認します。

[root@control ~]# helm list -n harbor
NAME    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
harbor  harbor          1               2026-07-12 11:24:12.830505033 +0900 JST deployed        harbor-1.19.1   2.15.1

HarborのPodが正常に起動したことを確認します。

[root@control ~]# kubectl get pods -n harbor
NAME                                 READY   STATUS    RESTARTS      AGE
harbor-core-767d58bf54-6kh8f         1/1     Running   0             2m32s
harbor-database-0                    1/1     Running   0             2m32s
harbor-jobservice-84b7976678-l88l5   1/1     Running   4 (80s ago)   2m32s
harbor-portal-5b6bc45d5c-wtwd6       1/1     Running   0             2m32s
harbor-redis-0                       1/1     Running   0             2m32s
harbor-registry-54f749db94-x2spw     2/2     Running   0             2m32s
harbor-trivy-0                       1/1     Running   0             2m32s

Harborでは、コンテナイメージやデータベース、Redisなどの永続データをPersistentVolumeClaim(PVC)に保存します。すべてのPVCが Bound になっていれば、PersistentVolumeが正常に割り当てられています。

[root@control ~]# kubectl get pvc -n harbor
NAME                              STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
data-harbor-redis-0               Bound    pvc-b5442603-8404-481e-8b93-938faf3e785e   1Gi        RWO            local-path     <unset>                 3m7s
data-harbor-trivy-0               Bound    pvc-b5fe7ad0-3510-44cc-a733-cb84a8f17eea   5Gi        RWO            local-path     <unset>                 3m7s
database-data-harbor-database-0   Bound    pvc-49907738-2af0-4864-b698-81f58eba1839   1Gi        RWO            local-path     <unset>                 3m7s
harbor-jobservice                 Bound    pvc-d236097f-637c-4525-b4bb-ecf4c7f9ed5e   1Gi        RWO            local-path     <unset>                 3m7s
harbor-registry                   Bound    pvc-f9800439-4cb1-4cca-b57b-a80b3ae96771   5Gi        RWO            local-path     <unset>                 3m7s

Harbor用のIngressリソースが作成されていることを確認します。HOSTSにharbor.home.lab、ADDRESSにMetalLBから割り当てられたIPアドレスが表示されていれば、Ingressが正しく構成されています。

[root@control ~]# kubectl get ingress -n harbor
NAME             CLASS   HOSTS             ADDRESS         PORTS     AGE
harbor-ingress   nginx   harbor.home.lab   192.168.1.200   80, 443   34h

10 Harbor管理画面へのログイン

ブラウザで https://harbor.home.lab/ にアクセスします。
ログイン画面が表示されたら、ユーザー名に 「admin」、パスワードに 「Harbor12345」 を入力してログインします。パスワードには、values.yaml の harborAdminPassword に設定した値を使用します。

ログインに成功すると、以下のようにHarborの管理画面が表示されます。

Z 参考図書

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

単行本

電子書籍

Volume(emptyDir/hostPath/PV・PVC)の挙動を確かめてみた

1 Volumeとは

KubernetesのVolumeとは、Pod内のコンテナがデータを保存・共有するための仕組みです。通常、コンテナのルートファイルシステムは一時的であり、コンテナが再生成されるとデータは失われます。Volumeを利用することで、次のようなことが可能になります。

  • コンテナの再起動後もデータを保持できる(Volumeの種類による)
  • 同一Pod内の複数コンテナ間でデータを共有できる
  • ノード上のディスクや、NFS、クラウドストレージなどの外部ストレージをコンテナから利用できる

VolumeはPod単位で定義され、各コンテナにマウントして使用します。Volumeにはさまざまな種類があり、emptyDir や hostPath のようにPodに直接定義するものと、PersistentVolumeClaim(PVC) を介して PersistentVolume(PV) を利用するものがあります。Volumeの種類によっては、Podが削除・再作成された後もデータを保持できます。

(1) Volumeタイプ(Podから直接指定するもの)

種類 概要 用途
emptyDir Pod起動時に作成され、Pod削除時に消える一時領域 一時ファイル
hostPath ノードのローカルディレクトリを直接マウントする。ストレージ変更時はPodの定義修正が必要 デバッグや単一ノードでの検証用。Podが別ノードに移動するとデータが引き継げないため、本番環境での利用は非推奨
nfs NFSサーバのディレクトリをマウント 複数Pod間での共有ストレージ

(2) 永続ストレージを抽象化する仕組み(PV/PVC)
PodはPVCを指定するだけで、背後でどのストレージ(hostPathなのかNFSなのか等)が使われているかを意識せずにVolumeを利用できます。

  • PV(PersistentVolume):ストレージ実体(例:NFS, クラウドディスクなど)をKubernetesリソースとして定義したもの
  • PVC(PersistentVolumeClaim):利用者が必要な容量・アクセスモードを要求し、PodからPVを利用するための窓口

2 検証環境

2.1 ネットワーク構成

検証環境は3台の仮想マシンでKubernetesクラスタを構成しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|               |    |               |   |               |
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.2                  |.139               |.171
        |                    |                   |
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|                           KVM                          |
+--------------------------------------------------------+

それぞれの役割は以下のとおりです。
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.35.3
Kustomize Version: v5.7.1
Server Version: v1.35.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 emptyDirの使い方

emptyDirは、Podが起動したときに作成され、Podが削除されると消える一時的なVolumeです。ここでは、以下について確認します。

  • Pod内のコンテナ間でファイル共有ができること
  • Podが削除されるとデータも削除されること

まず、YAMLファイルを作成します。このPodにはcontainer1(nginx)とcontainer2(busybox)という2つのコンテナが定義されており、両方が同じemptyDirボリュームshared-volumeを/dataにマウントしています。

[root@control ~]# vi emptydir.yaml
[root@control ~]# cat emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-test
spec:
  containers:
  - name: container1
    image: nginx
    volumeMounts:
    - name: shared-volume
      mountPath: /data

  - name: container2
    image: busybox
    command: ["sleep", "3600"]
    volumeMounts:
    - name: shared-volume
      mountPath: /data

  volumes:
  - name: shared-volume
    emptyDir: {}
[root@control ~]#

YAMLファイルの内容をKubernetesに適用し、Podを作成します。

[root@control ~]# kubectl apply -f emptydir.yaml
pod/emptydir-test created

Podの状態を確認します。READY が「2/2」、STATUS が「Running」となっていることから、Pod内の2つのコンテナが両方とも正常に動作していることがわかります。

[root@control ~]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE     IP              NODE      NOMINATED NODE   READINESS GATES
emptydir-test   2/2     Running   0          3m17s   10.244.189.65   worker2   <none>           <none>

container1(nginxコンテナ)にログインします。

[root@control ~]# kubectl exec -it emptydir-test -c container1 -- /bin/bash

ログインしたら、emptyDirにマウントされた /data にファイルを作成します。

root@emptydir-test:/# echo "hello" > /data/test.txt
root@emptydir-test:/# cat /data/test.txt
hello
root@emptydir-test:/#

container1から抜けます。

root@emptydir-test:/# exit
exit

次に、container2(busyboxコンテナ)にログインします。

[root@control ~]# kubectl exec -it emptydir-test -c container2 -- /bin/sh

ログインしたら、container1で作成したファイルを確認します。container1で作成したファイルと同じ内容のファイルが確認できるので、emptyDirでデータ共有できていることがわかります。

/ # cat /data/test.txt
hello
/ #

container2から抜けます。

/ # exit
[root@control ~]#

次に、コンテナで作成したファイルがノード上のどこに保存されているのかを確認します。

まず、Podの詳細情報を取得し、その中からUIDを抽出します。このUIDは、ノード上のディレクトリ名として使用されます。

[root@control ~]# kubectl get pod emptydir-test -o yaml | grep uid
  uid: 8d8f6017-c297-4fe7-ba80-3c0c21a75c6d
        uid: 0
        uid: 0

次に、kubeletが管理しているディレクトリ配下に移動し、emptyDirの実体を確認します。

[root@worker2 shared-volume]# pwd
/var/lib/kubelet/pods/8d8f6017-c297-4fe7-ba80-3c0c21a75c6d/volumes/kubernetes.io~empty-dir/shared-volume

このディレクトリ内に、コンテナ内で作成したファイルが存在するか確認します。

[root@worker2 shared-volume]# ls
test.txt

ファイルの内容を確認すると、コンテナ内で作成した内容と一致していることがわかります。このことから、コンテナ内の /data に作成したファイルは、実際にはノード上の /var/lib/kubelet/ 配下に保存されていることがわかります。

[root@worker2 shared-volume]# cat test.txt
hello

最後に、Podを削除します。

[root@control ~]# kubectl delete -f emptydir.yaml

Podを削除したあとに /var/lib/kubelet/pods 配下を確認すると、作成した emptyDir のデータも削除されていることがわかります。このことから、emptyDir は Pod のライフサイクルに紐づいた一時的な領域であり、Pod が起動している間だけ有効であることが確認できます。なお、コンテナが再起動した場合は Pod は存続しているため、emptyDir のデータは保持されます。一方、Pod が削除されると、emptyDir のデータも削除されます。

4 hostPathの使い方

hostPath確認用のYAMLファイルを作成します。
このYAMLでは、ワーカーノード上の /tmp/hostpath-data 配下のディレクトリをコンテナ内の /data にマウントします。hostPath はノード上のローカルディレクトリをマウントするため、データは「Pod」ではなく「ノード」に紐づきます。このため、同じノードに再スケジュールされた場合は、以前保存したファイルをそのまま参照できますが、異なるノードに Pod が移動した場合は、移動前に別ノード上で hostPath に保存したデータを参照できません。なお、type: DirectoryOrCreate を指定することで、対象のディレクトリが存在しない場合は自動的に作成されます。

[root@control ~]# vi hostpath.yaml
[root@control ~]# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hostpath-test
spec:
  containers:
  - name: container1
    image: nginx
    volumeMounts:
    - name: shared-volume
      mountPath: /data

  - name: container2
    image: busybox
    command: ["sleep", "3600"]
    volumeMounts:
    - name: shared-volume
      mountPath: /data

  volumes:
  - name: shared-volume
    hostPath:
      path: /tmp/hostpath-data
      type: DirectoryOrCreate

ちなみに、「3 emptyDirの使い方」で作成したYAMLファイルとの差分は以下のとおりです。

[root@control ~]# diff -Nur emptydir.yaml hostpath.yaml
--- emptydir.yaml       2026-03-23 14:52:36.262665552 +0900
+++ hostpath.yaml       2026-03-23 15:07:49.462094598 +0900
@@ -1,7 +1,7 @@
 apiVersion: v1
 kind: Pod
 metadata:
-  name: emptydir-test
+  name: hostpath-test
 spec:
   containers:
   - name: container1
@@ -19,4 +19,6 @@

   volumes:
   - name: shared-volume
-    emptyDir: {}
+    hostPath:
+      path: /tmp/hostpath-data
+      type: DirectoryOrCreate

YAMLファイルを適用し、hostPathを利用するPodを作成します。

[root@control ~]# kubectl apply -f hostpath.yaml
pod/hostpath-test created

Podの状態や配置されたノードを確認します。READY が「2/2」、STATUS が「Running」となっていることから、2つのコンテナが正常に動作していることがわかります。

[root@control ~]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
hostpath-test   2/2     Running   0          75s   10.244.189.67   worker2   <none>           <none>

container1(nginxコンテナ)にログインします。

[root@control ~]# kubectl exec -it hostpath-test -c container1 -- /bin/bash

hostPathでマウントされた /data 配下にファイルを作成し、内容を確認します。

root@hostpath-test:/# echo "hello" > /data/test.txt
root@hostpath-test:/# cat /data/test.txt
hello
root@hostpath-test:/# exit
exit

container2(busyboxコンテナ)にログインします。

[root@control ~]# kubectl exec -it hostpath-test -c container2 -- /bin/sh

container1で作成したファイルが参照できることを確認します。

/ # cat /data/test.txt
hello
/ # exit
[root@control ~]#

ワーカーノード上のディレクトリに、同じファイルが作成されていることを確認します。

[root@worker2 ~]# cat /tmp/hostpath-data/test.txt
hello

作成したPodを削除します。

[root@control ~]# kubectl delete -f hostpath.yaml
pod "hostpath-test" deleted from default namespace

Podが削除されていることを確認します。

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

ノード上にファイルが残っていることを確認します。

[root@worker2 ~]# cat /tmp/hostpath-data/test.txt
hello

次の検証のため、作成したPodを削除します。

[root@control ~]# kubectl delete -f hostpath.yaml
pod "hostpath-test" deleted from default namespace

5 PV(PersistentVolume)/PVC(PersistentVolumeClaim)の使い方

PVおよびPVCは、永続ストレージを管理するための仕組みです。前章で説明したhostPathはストレージのパスが変更されるとすべてのPodの定義を修正する必要があるなど、運用面での課題があります。これに対し、PV/PVCを利用することでストレージを抽象化し、Podからストレージの詳細を切り離すことができます。PodはPVCのみを参照するため、ストレージの変更はPV側で吸収でき、Podの定義を変更する必要がありません。本章では、PV/PVCの基本的な動きを理解するため、あえてバックエンドにhostPathを使った構成で検証を行います。

種類 概要 作成者
PV ストレージ本体(データの保存領域) 管理者
PVC PVを使うための申請。PodとPVをつなぐ役割 利用者

(1) 管理者側(PVの作成)
PV(PersistentVolume)を定義するためのYAMLファイルを作成します。このYAMLでは、ノード上の /tmp/pv-data をストレージとして使用するPVを定義しています。

[root@control ~]# vi pv.yaml
[root@control ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-test
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /tmp/pv-data

YAMLファイルをKubernetesに適用し、PersistentVolumeを作成します。

[root@control ~]# kubectl apply -f pv.yaml
persistentvolume/pv-test created

作成したPVの状態を確認します。STATUS が Available となっており、まだどのPVCにもバインドされていないことがわかります。

[root@control ~]# kubectl get pv -o wide
NAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE   VOLUMEMODE
pv-test   1Gi        RWO            Retain           Available                          <unset>                          59s   Filesystem

(2) 利用者側(PVCの作成)

次に、先ほど作成したPVを利用するためのPVC(PersistentVolumeClaim)を作成します。PVCでは、必要な容量やアクセスモードを指定します。

[root@control ~]# vi pvc.yaml
[root@control ~]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-test
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

YAMLファイルを適用してPVCを作成します。

[root@control ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/pvc-test created

PVCを作成すると、条件に合うPVが自動的に割り当てられます。PVとPVCの状態を確認し、Bound になっていることを確認します。

[root@control ~]# kubectl get pv,pvc -o wide
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE   VOLUMEMODE
persistentvolume/pv-test   1Gi        RWO            Retain           Bound    default/pvc-test                  <unset>                          44m   Filesystem

NAME                             STATUS   VOLUME    CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE    VOLUMEMODE
persistentvolumeclaim/pvc-test   Bound    pv-test   1Gi        RWO                           <unset>                 114s   Filesystem

(3) 利用者側(Podの作成)
PVにhostPathを使っているため、実際のデータ保存場所は /tmp/pv-data になります。挙動としてはhostPathと同様ですが、PV/PVCによりストレージの場所をPodから隠蔽できる点が異なります。これにより、Podを変更せずにストレージを差し替えることが可能となります。

PVCを利用するPodを定義するYAMLファイルを作成します。このPodでは、pvc-test を /data にマウントして使用します。

[root@control ~]# vi pod.yaml
[root@control ~]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pvc-test-pod
spec:
  containers:
  - name: busybox
    image: busybox
    command: ["sleep", "3600"]
    volumeMounts:
    - name: storage
      mountPath: /data

  volumes:
  - name: storage
    persistentVolumeClaim:
      claimName: pvc-test

YAMLファイルを適用し、PVCを利用するPodを作成します。

[root@control ~]# kubectl apply -f pod.yaml
pod/pvc-test-pod created

Podの状態を確認します。STATUS が Running となり、正常に起動していることがわかります。

[root@control ~]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
pvc-test-pod   1/1     Running   0          19s   10.244.235.145   worker1   <none>           <none>

作成したPodにログインし、/data にファイルを作成します。

[root@control ~]# kubectl exec -it pvc-test-pod -- /bin/sh
/ # echo "hello world" > /data/test.txt
/ # cat /data/test.txt
hello world

Pod から抜けます。

/ # exit

ノード上の /tmp/pv-data を確認し、コンテナ内で作成したファイルが保存されていることを確認します。

[root@worker1 ~]# cat /tmp/pv-data/test.txt
hello world

次に、Podを削除してもデータが残ることを確認します。まずPodを削除します。

[root@control ~]# kubectl delete -f pod.yaml
pod "pvc-test-pod" deleted from default namespace

Podが削除されていることを確認します。

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

同じ定義のPodを再度作成します。

[root@control ~]# kubectl apply -f pod.yaml
pod/pvc-test-pod created

再作成したPodが Running になっていることを確認します。

[root@control ~]# kubectl get pods -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
pvc-test-pod   1/1     Running   0          28s   10.244.235.146   worker1   <none>           <none>

再度Podにログインし、以前作成したファイルが残っていることを確認します。

[root@control ~]# kubectl exec -it pvc-test-pod -- /bin/sh
/ # cat /data/test.txt
hello world

今回の検証では、Podが再度同じ worker1 にスケジュールされたためデータを引き継げています。hostPathを使用したPVの場合、別ノードにスケジュールされるとデータは参照できなくなるため、実運用ではネットワークストレージ(NFSやクラウドの永続ディスクなど)や、NodeAffinityを設定したLocal Volumeが使われます。

最後に、検証で使用したPod、PVC、PVを削除してクリーンアップします。

[root@control ~]# kubectl delete -f pod.yaml
pod "pvc-test-pod" deleted from default namespace
[root@control ~]# kubectl delete -f pvc.yaml
persistentvolumeclaim "pvc-test" deleted from default namespace
[root@control ~]# kubectl delete -f pv.yaml
persistentvolume "pv-test" deleted

Z 参考図書

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

単行本

電子書籍

KubernetesのNamespaceを理解する~ Leaseオブジェクトはどのように更新されるのか

1 Namespaceとは

Namespaceは、単一のクラスタ内を論理的に分割する仕組みです。チームや環境(開発・本番など)ごとにリソースを分けて管理できるため、複数のチームでクラスタを共有する際に役立ちます。たとえば、チームAとチームBが同じクラスタを使用している場合、チームAは team-a、チームBは team-b といったNamespaceをそれぞれ利用することで、お互いのリソース名が衝突することなく独立して開発を進めることができます。なお、PodやServiceなどのリソースはNamespaceごとに分離されますが、Nodeやストレージ(PersistentVolume)などはクラスタ全体で共有されるリソースです。

2 検証環境

2.1 ネットワーク構成

検証環境は3台の仮想マシンでKubernetesクラスタを構成しています。

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|               |    |               |   |               |
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.19                 |.20                |.22
        |                    |                   |
        |                    |                   |
        |   192.168.1.0/24   |                   |
+--------------------------------------------------------+
|                           KVM                          |
+--------------------------------------------------------+

それぞれの役割は以下のとおりです。
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.35.3
Kustomize Version: v5.7.1
Server Version: v1.35.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 Namespaceの使い方

3.1 Namespace一覧の確認方法

クラスター内のNamespace一覧を表示します。

[root@control ~]# kubectl get namespaces
NAME              STATUS   AGE
default           Active   10d
kube-node-lease   Active   10d
kube-public       Active   10d
kube-system       Active   10d
Namespace 説明
default Namespaceを指定しなかった場合に使われる、デフォルトのNamespace
kube-system Kubernetesシステムが作成するオブジェクトのためのNamespace
kube-public 未認証のクライアントも含め、全員が読み取り可能なNamespace。クラスター全体に公開したい情報を置く用途を想定しているが、公開は運用上の慣習であり必須ではない
kube-node-lease 各ノードに対応するLeaseオブジェクトを格納するNamespace。各ノードのkubeletがLeaseオブジェクトを定期的に更新し、コントロールプレーンはその更新状況を監視してノードの生存状態を確認する

kubernetes.io

3.2 Namespaceの作成・削除方法

team-aという名前のNamespaceを作成します。

[root@control ~]# kubectl create namespace team-a
namespace/team-a created

再度、クラスタに存在するNamespace一覧を表示します。team-a というNamespaceが新しく作成されたことが確認できます。

[root@control ~]# kubectl get namespaces
NAME              STATUS   AGE
default           Active   10d
kube-node-lease   Active   10d
kube-public       Active   10d
kube-system       Active   10d
team-a            Active   9s

team-aという名前のNamespaceを削除します。

[root@control ~]# kubectl delete namespaces team-a
namespace "team-a" deleted

再度、クラスタに存在するNamespaceの一覧を表示します。team-a というNamespaceが削除されたことが確認できます。

[root@control ~]# kubectl get namespaces
NAME              STATUS   AGE
default           Active   10d
kube-node-lease   Active   10d
kube-public       Active   10d
kube-system       Active   10d

3.3 指定したNamespaceにリソースを作成する方法

(1) リソースの作成
ここでは、team-a と team-b というNamespaceを作成し、それぞれでPodを起動してみます。

team-aという名前のNamespaceを作成します。

[root@control ~]# kubectl create namespace team-a
namespace/team-a created

team-bという名前のNamespaceを作成します。

[root@control ~]# kubectl create namespace team-b
namespace/team-b created

Namespace一覧を確認します。

[root@control ~]# kubectl get namespaces
NAME              STATUS   AGE
default           Active   11d
kube-node-lease   Active   11d
kube-public       Active   11d
kube-system       Active   11d
team-a            Active   87s
team-b            Active   85s

default のnamespaceでNginxのPodを起動します。

[root@control ~]# kubectl run nginx-default --image=nginx
pod/nginx-default created

team-a のnamespaceでNginxのPodを起動します。

[root@control ~]# kubectl run nginx-a --image=nginx --namespace=team-a
pod/nginx-a created

team-b のnamespaceでNginxのPodを起動します。

[root@control ~]# kubectl run nginx-b --image=nginx --namespace=team-b
pod/nginx-b created

defaultのnamespaceでNginxのPodが動作していることが確認できます。

[root@control ~]# kubectl get pods
NAME            READY   STATUS    RESTARTS   AGE
nginx-default   1/1     Running   0          65s

team-aという名前のnamespaceでNginxのPodが動作していることが確認できます。

[root@control ~]# kubectl get pods --namespace=team-a
NAME      READY   STATUS    RESTARTS   AGE
nginx-a   1/1     Running   0          45s

team-bという名前のnamespaceでNginxのPodが動作していることが確認できます。

[root@control ~]# kubectl get pods --namespace=team-b
NAME      READY   STATUS    RESTARTS   AGE
nginx-b   1/1     Running   0          37s

-A オプションを使用すると、すべてのNamespaceに存在するリソースを確認することができます。以下の例では、NginxのPodが default、team-a、team-b の各Namespaceで動作していることが確認できます。

[root@control ~]# kubectl get pods -A
NAMESPACE     NAME                                     READY   STATUS    RESTARTS       AGE
default       nginx-default                            1/1     Running   0              3m31s
kube-system   calico-kube-controllers-9dff488b-sxpdb   1/1     Running   13 (21m ago)   23d
kube-system   calico-node-7m6ph                        1/1     Running   13 (21m ago)   23d
kube-system   calico-node-vcmh5                        1/1     Running   13 (21m ago)   23d
kube-system   calico-node-w7ldj                        1/1     Running   13 (21m ago)   23d
kube-system   coredns-66869746d6-45g95                 1/1     Running   2 (21m ago)    2d23h
kube-system   coredns-66869746d6-zk5p6                 1/1     Running   2 (21m ago)    2d23h
kube-system   etcd-control                             1/1     Running   26 (21m ago)   23d
kube-system   kube-apiserver-control                   1/1     Running   25 (21m ago)   23d
kube-system   kube-controller-manager-control          1/1     Running   15 (21m ago)   23d
kube-system   kube-proxy-426t6                         1/1     Running   13 (21m ago)   23d
kube-system   kube-proxy-gr68g                         1/1     Running   13 (21m ago)   23d
kube-system   kube-proxy-krgq6                         1/1     Running   13 (21m ago)   23d
kube-system   kube-scheduler-control                   1/1     Running   15 (21m ago)   23d
team-a        nginx-a                                  1/1     Running   0              91s
team-b        nginx-b                                  1/1     Running   0              74s


(2) リソースの削除

Namespace team-a に存在するPodを削除します。

[root@control ~]# kubectl delete pod nginx-a -n team-a
pod "nginx-a" deleted from team-a namespace

Namespace team-b に存在するPodを削除します。

[root@control ~]# kubectl delete pod nginx-b -n team-b
pod "nginx-b" deleted from team-b namespace

default Namespaceに存在するPodを削除します。Namespaceを指定しない場合は、default が対象となります。

[root@control ~]# kubectl delete pod nginx
pod "nginx" deleted from default namespace

すべてのNamespaceのPod一覧を確認し、対象のPodが削除されていることを確認します。

[root@control ~]# kubectl get pod -A
NAMESPACE     NAME                                     READY   STATUS    RESTARTS       AGE
kube-system   calico-kube-controllers-9dff488b-sxpdb   1/1     Running   13 (50m ago)   23d
kube-system   calico-node-7m6ph                        1/1     Running   13 (49m ago)   23d
kube-system   calico-node-vcmh5                        1/1     Running   13 (50m ago)   23d
kube-system   calico-node-w7ldj                        1/1     Running   13 (49m ago)   23d
kube-system   coredns-66869746d6-45g95                 1/1     Running   2 (50m ago)    3d
kube-system   coredns-66869746d6-zk5p6                 1/1     Running   2 (49m ago)    3d
kube-system   etcd-control                             1/1     Running   26 (50m ago)   23d
kube-system   kube-apiserver-control                   1/1     Running   25 (50m ago)   23d
kube-system   kube-controller-manager-control          1/1     Running   15 (50m ago)   23d
kube-system   kube-proxy-426t6                         1/1     Running   13 (49m ago)   23d
kube-system   kube-proxy-gr68g                         1/1     Running   13 (50m ago)   23d
kube-system   kube-proxy-krgq6                         1/1     Running   13 (49m ago)   23d
kube-system   kube-scheduler-control                   1/1     Running   15 (50m ago)   23d

3.4 デフォルトNamespaceの確認・変更

現在のデフォルトNamespaceを確認します。この時点ではNamespaceが設定されていないため、何も表示されません(デフォルトでは default Namespaceが使用されます)。

[root@control ~]# kubectl config view --minify | grep namespace:
[root@control ~]#

デフォルトのNamespaceを team-a に変更します。

[root@control ~]# kubectl config set-context --current --namespace=team-a
Context "kubernetes-admin@kubernetes" modified.

再度、現在のデフォルトNamespaceを確認します。team-a に変更されていることが確認できます。

[root@control ~]# kubectl config view --minify | grep namespace:
    namespace: team-a

続いて、デフォルトのNamespaceを team-b に変更します。

[root@control ~]# kubectl config set-context --current --namespace=team-b
Context "kubernetes-admin@kubernetes" modified.

現在のデフォルトNamespaceを確認すると、team-b に変更されていることが確認できます。

[root@control ~]# kubectl config view --minify|grep namespace
    namespace: team-b

この状態で kubectl get pods を実行すると、デフォルトのNamespaceである team-b のPodが表示されます。

[root@control ~]# kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
nginx-b   1/1     Running   0          11m

--namespace オプションを指定することで、他のNamespaceのPodを確認することもできます。まず、default NamespaceのPodを確認します。

[root@control ~]# kubectl get pods --namespace=default
NAME            READY   STATUS    RESTARTS   AGE
nginx-default   1/1     Running   0          12m

続いて、team-a NamespaceのPodを確認します。

[root@control ~]# kubectl get pods --namespace=team-a
NAME      READY   STATUS    RESTARTS   AGE
nginx-a   1/1     Running   0          12m

4 kube-node-leaseについて

kube-node-lease Namespaceの使用目的は、各ノードに対応するLeaseオブジェクトを格納することです。Leaseオブジェクトは、各ノードのkubeletによって定期的に更新され、ノードの生存監視(ハートビート)に使用されます。各ノードのkubeletは、自身に対応するLeaseオブジェクトが存在しない場合、Leaseオブジェクトを作成します。作成要求はkube-apiserver経由で送信され、Leaseオブジェクトはetcdに保存されます。その後、kubeletは一定間隔(デフォルトでは約10秒ごと)で、kube-apiserver経由でLeaseオブジェクトのspec.renewTimeを更新します。更新されたLeaseオブジェクトは、kube-apiserverによってetcdに保存されます。

万一、kubeletの停止やノード障害、ネットワーク障害などによりspec.renewTimeが更新されなくなると、コントロールプレーンはLeaseオブジェクトの更新が一定時間行われていないことを検知します。更新がleaseDurationSeconds(デフォルトでは40秒)を超えて途絶えると、Node ControllerはそのノードをNotReady状態と判断します。その後、その状態が継続すると、Node Controllerはそのノード上で動作しているPodを別の正常なノードへ再スケジュール(再配置)する処理を開始します。

kube-node-lease Namespaceに格納されているLeaseオブジェクトを確認します。ノードごとに1つのLeaseオブジェクトが存在し、Leaseオブジェクトの名前がノード名と同じであることがわかります。

[root@control ~]# kubectl get leases -n kube-node-lease
NAME      HOLDER    AGE
control   control   29d
worker1   worker1   29d
worker2   worker2   3d1h

次に、worker1ノードに対応するLeaseオブジェクトの内容を確認します。

[root@control ~]# kubectl get lease worker1 -n kube-node-lease -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
  creationTimestamp: "2026-06-05T05:37:30Z"
  name: worker1
  namespace: kube-node-lease
  ownerReferences:
  - apiVersion: v1
    kind: Node
    name: worker1
    uid: 8d828898-9282-4acd-b556-ceb068c8ff08
  resourceVersion: "132079"
  uid: a8fa8344-4044-47ff-9d3d-b94713364b29
spec:
  holderIdentity: worker1
  leaseDurationSeconds: 40
  renewTime: "2026-07-04T12:47:54.251743Z"

renewTimeの更新間隔を確認するため、LeaseオブジェクトのrenewTimeを1秒ごとに表示するrenewtime.shを作成して実行しました。
実行結果を見ると、renewTimeが約10秒ごとに更新されていることがわかります。このrenewTimeは、worker1ノード上で動作するkubeletによって定期的に更新されます。

[root@control ~]#  ./renewtime.sh
09:22:58   renewTime: "2026-07-05T00:22:54.205486Z"
09:22:59   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:00   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:01   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:02   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:03   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:04   renewTime: "2026-07-05T00:22:54.205486Z"
09:23:05   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:06   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:07   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:08   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:09   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:10   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:11   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:12   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:13   renewTime: "2026-07-05T00:23:04.473769Z"
09:23:14   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:15   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:16   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:17   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:19   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:20   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:21   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:22   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:23   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:24   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:25   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:26   renewTime: "2026-07-05T00:23:24.830596Z"

kubeletサービスを停止します。

[root@worker1 ~]# systemctl stop kubelet.service

kubeletサービスを停止すると、renewTimeが更新されなくなることが確認できます。これは、Leaseオブジェクトの更新をkubeletが行っていることを示しています。

[root@control ~]#  ./renewtime.sh
-snip-
09:23:23   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:24   renewTime: "2026-07-05T00:23:14.799886Z"
09:23:25   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:26   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:27   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:28   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:29   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:30   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:31   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:32   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:33   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:34   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:35   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:36   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:37   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:38   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:39   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:40   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:41   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:42   renewTime: "2026-07-05T00:23:24.830596Z"
09:23:44   renewTime: "2026-07-05T00:23:24.830596Z"

kubeletサービスを停止すると、LeaseオブジェクトのrenewTimeの更新が停止します。その後、一定時間が経過すると、kube-controller-managerが更新が止まったことを検知し、worker1ノードをNotReadyと判定します。

[root@control ~]# date;kubectl get nodes
2026年  7月  5日 日曜日 09:39:10 JST
NAME      STATUS   ROLES           AGE     VERSION
control   Ready    control-plane   29d     v1.35.5
worker1   Ready    <none>          29d     v1.35.5
worker2   Ready    <none>          3d13h   v1.35.5

[root@control ~]# date;kubectl get nodes
2026年  7月  5日 日曜日 09:39:16 JST
NAME      STATUS   ROLES           AGE     VERSION
control   Ready    control-plane   29d     v1.35.5
worker1   Ready    <none>          29d     v1.35.5
worker2   Ready    <none>          3d13h   v1.35.5

[root@control ~]# date;kubectl get nodes
2026年  7月  5日 日曜日 09:39:19 JST
NAME      STATUS     ROLES           AGE     VERSION
control   Ready      control-plane   29d     v1.35.5
worker1   NotReady   <none>          29d     v1.35.5
worker2   Ready      <none>          3d13h   v1.35.5

Z 参考図書

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

単行本

電子書籍