hana_shinのLinux技術ブログ

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

プライバシーポリシー

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


運営者:hana-shin

初出掲載:2019年05月05日

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

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

単行本

電子書籍

ConfigMap/Secretの使い方

1 ConfigMap/Secretとは

種別 概要
ConfigMap アプリケーションの設定を管理するKubernetesオブジェクトです。設定はコンテナイメージには含めず、外部リソースとして管理します。そのため、コンテナイメージを作り直すことなく設定を変更できます。なお、変更内容の反映方法は、ConfigMapの利用方法(Volumeとしてマウントするか、環境変数として参照するか)によって異なります。3章では、その違いを実機で確認します。
Secret アプリケーションの機密情報(パスワードやAPIキーなど)を管理するためのKubernetesオブジェクトです。機密情報はコンテナイメージの中には含めず、外部リソースとして管理します。そのため、コンテナイメージを作り直すことなく機密情報だけを変更できます。変更内容の反映方法は、Secretの利用方法(Volumeとしてマウントするか、環境変数として参照するか)によって異なります。Secretはbase64形式で保存されますが、暗号化されているわけではありません。

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 ConfigMapの利用方法

3.1 Volumeとしてマウントする

(1) ConfigMapを新規作成

ConfigMapのマニフェストファイルを作成します。ここでは、Webサーバ(nginx)が表示するHTMLファイル(index.html)をConfigMapに保存します。なお、マニフェストファイル中の|(リテラルブロックスケーラー)は、改行を保持したまま、複数行の文字列を値として記述できます。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-html
data:
  index.html: |
    good morning!
    good morning!

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

[root@control ~]# kubectl apply -f configmap.yaml
configmap/nginx-html created

ConfigMap(nginx-html)が作成されたことを確認します。

[root@control ~]# kubectl get configmaps
NAME               DATA   AGE
kube-root-ca.crt   1      24d
nginx-html         1      11s

ConfigMapをVolumeとしてPodにマウントするDeploymentのマニフェストファイルを作成します。

[root@control ~]# vi deploy-configmap.yaml
[root@control ~]# cat deploy-configmap.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
          readOnly: true
      volumes:
      - name: html-volume
        configMap:
          name: nginx-html

Deploymentを作成します。

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

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

[root@control ~]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
nginx-deploy-757844654-wkcq4   1/1     Running   0          13s   10.244.189.68   worker2   <none>           <none>

ローカル環境からPodへアクセスするため、kubectl port-forwardを実行します。

[root@control ~]# kubectl port-forward nginx-deploy-757844654-wkcq4 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

別ターミナルを開いて、アクセス確認を行います。

[root@control ~]# curl http://localhost:8080
good morning!
good morning!

(2) ConfigMapの更新(Pod再起動なしで自動反映を確認)
ConfigMapのマニフェストファイルを「good morning!」から「good evening!」へ変更します。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-html
data:
  index.html: |
    good evening!
    good evening!

更新したマニフェストファイルをKubernetesクラスタに適用し、ConfigMapを更新します。新規作成時はcreatedと表示されましたが、既存のConfigMapを更新する場合はconfiguredと表示されます。

[root@control ~]# kubectl apply -f configmap.yaml
configmap/nginx-html configured

Podを再起動せず、kubectl port-forwardを実行したままの状態で、再度アクセスします。

[root@control ~]# curl http://localhost:8080
good evening!
good evening!

最初に起動したPodを再起動・再作成することなく、ConfigMapの変更内容が反映されたことを確認できました。ConfigMapをVolumeとしてマウントしている場合、Kubernetesは変更内容をPod内へ自動的に反映します。ただし、本検証環境では反映まで約1分かかりました。一方、ConfigMapを環境変数として参照している場合は、値は自動更新されず、変更を反映するにはPodの再起動が必要です。

(3) あと始末
検証で使用したDeploymentとConfigMapを削除し、環境をクリーンな状態に戻します。

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

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

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

[root@control ~]# kubectl delete -f configmap.yaml
configmap "nginx-html" deleted from default namespace

3.2 環境変数として参照する

(1) ConfigMapを新規作成

環境変数として利用するConfigMapを作成します。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  MESSAGE: "good morning!"

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

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

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

[root@control ~]# kubectl get configmaps
NAME               DATA   AGE
app-config         1      13s
kube-root-ca.crt   1      27d

ConfigMapを環境変数として参照するDeploymentのマニフェストファイルを作成します。

[root@control ~]# vi deploy-configmap.yaml
[root@control ~]# cat deploy-configmap.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        env:
        - name: MESSAGE
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: MESSAGE

Deploymentを作成します。

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

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

[root@control ~]# kubectl get pods -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deploy-89768bd6f-qnp4k   1/1     Running   0          57s   10.244.235.133   worker1   <none>           <none>

Podへログインします。

[root@control ~]# kubectl exec -it nginx-deploy-89768bd6f-qnp4k -- bash

ConfigMapの値が環境変数 MESSAGE として設定されていることを確認します。

root@nginx-deploy-89768bd6f-qnp4k:/# echo $MESSAGE
good morning!

(2) ConfigMapの更新
別のターミナルを開き、ConfigMapを更新します。

[root@control ~]# vi configmap.yaml
[root@control ~]# cat configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  MESSAGE: "good evening!"

更新したマニフェストファイルを適用し、ConfigMapを更新します。

[root@control ~]# kubectl apply -f configmap.yaml
configmap/app-config configured

ConfigMapを更新したあと、環境変数の値を確認します。

root@nginx-deploy-89768bd6f-qnp4k:/# date;echo $MESSAGE
Thu Jul  2 11:15:03 UTC 2026
good morning!

ConfigMapを更新しても、環境変数の値は変更されません。

root@nginx-deploy-89768bd6f-qnp4k:/# date;echo $MESSAGE
Thu Jul  2 11:17:05 UTC 2026
good morning!

コンテナからログアウトします。

root@nginx-deploy-89768bd6f-qnp4k:/# exit
exit
command terminated with exit code 127

(3) Deploymentを再起動して変更を反映する

Deploymentが存在することを確認します。

[root@control ~]# kubectl get deployments.apps
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deploy   1/1     1            1           14m

Deploymentをロールアウト再起動します。これにより、新しいPodが作成されます。

[root@control ~]# kubectl rollout restart deployment nginx-deploy
deployment.apps/nginx-deploy restarted

新しいPodが作成されたことを確認します。

[root@control ~]# kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP              NODE      NOMINATED NODE   READINESS GATES
nginx-deploy-864c85b6d4-8tzdd   1/1     Running   0          17s   10.244.189.67   worker2   <none>           <none>

新しいPodへログインします。環境変数 MESSAGE の値が更新されていることを確認します。

[root@control ~]# kubectl exec -it nginx-deploy-864c85b6d4-8tzdd -- bash
root@nginx-deploy-864c85b6d4-8tzdd:/# echo $MESSAGE
good evening!

コンテナからログアウトします。

root@nginx-deploy-864c85b6d4-8tzdd:/# exit
exit
command terminated with exit code 130

(4) あと始末
検証で使用したDeploymentとConfigMapを削除し、環境をクリーンな状態に戻します。

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

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

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

[root@control ~]# kubectl delete -f configmap.yaml
configmap "app-config" deleted from default namespace

4 Secretの使い方

(1) Secretを新規作成

Secretを作成するためのマニフェストファイルを作成します。stringDataを使うことで、平文のまま値を記述できます(適用時に自動でBase64エンコードされます)。

[root@control ~]# vi secret.yaml
[root@control ~]# cat secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nginx-html-secret
type: Opaque
stringData:
  index.html: |
    top secret!
    top secret!

作成したマニフェストファイルを適用し、Secretを作成します。

[root@control ~]# kubectl apply -f secret.yaml
secret/nginx-html-secret created

Secret(nginx-html-secret)が作成されたことを確認します。

[root@control ~]# kubectl get secrets
NAME                TYPE     DATA   AGE
nginx-html-secret   Opaque   1      3m54s

SecretをVolumeとしてマウントするDeploymentのマニフェストファイルを作成します。ConfigMapのときと同様に、mountPathはディレクトリとして指定し、subPathは使いません。

[root@control ~]# vi deploy-secret.yaml
[root@control ~]# cat deploy-secret.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: html-volume
          mountPath: /usr/share/nginx/html
          readOnly: true
      volumes:
      - name: html-volume
        secret:
          secretName: nginx-html-secret

Deploymentを作成します。

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

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

[root@control ~]# kubectl get pods -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deploy-6b9bcfd675-nlkh4   1/1     Running   0          21s   10.244.235.167   worker1   <none>           <none>

ローカル環境からPodへアクセスするため、kubectl port-forwardを実行します。

[root@control ~]# kubectl port-forward nginx-deploy-6b9bcfd675-nlkh4 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

別ターミナルを開いて、アクセス確認を行います。

[root@control ~]# curl http://localhost:8080
top secret!
top secret!

Secretの中身がBase64でエンコードされていることも確認しておきます。

[root@control ~]# kubectl get secret nginx-html-secret -o yaml
apiVersion: v1
data:
  index.html: dG9wIHNlY3JldCEKdG9wIHNlY3JldCEK
kind: Secret
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Secret","metadata":{"annotations":{},"name":"nginx-html-secret","namespace":"default"},"stringData":{"index.html":"top secret!\ntop secret!\n"},"type":"Opaque"}
  creationTimestamp: "2026-06-30T04:38:24Z"
  name: nginx-html-secret
  namespace: default
  resourceVersion: "1429701"
  uid: 8ca32f54-7868-415f-98b3-591dbe312e88
type: Opaque

表示されたBase64文字列(dG9wIHNlY3JldCEKdG9wIHNlY3JldCEK)をデコードし、元の平文と一致することを確認します。

[root@control ~]# echo dG9wIHNlY3JldCEKdG9wIHNlY3JldCEK | base64 -d
top secret!
top secret!

(2) Secretの更新(Pod再起動なしで自動反映を確認)

Secretの内容を「top secret!」から「Confidential!」へ変更します。

[root@control ~]# vi secret.yaml
[root@control ~]# cat secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: nginx-html-secret
type: Opaque
stringData:
  index.html: |
    Confidential!
    Confidential!

更新したマニフェストファイルを適用します。

[root@control ~]# kubectl apply -f secret.yaml
secret/nginx-html-secret configured

Podを再起動せず、kubectl port-forwardも実行したままの状態で、再度アクセスして内容を確認します。

[root@control ~]# curl http://localhost:8080
Confidential!
Confidential!

最初に起動した Pod(再起動・再作成はしていません)に対して、Secret の変更内容が自動的に反映されたことを確認できました。Secret も ConfigMap と同様に、Volume としてマウントしている場合は、Pod 内のファイルへ変更が自動的に反映されます。なお、Secret を環境変数として参照している場合は、値の変更は自動的には反映されず、Pod の再起動(再作成)が必要になります。自動反映されるのは、Secret を Volume としてマウントしている場合のみです。

(3) あと始末
検証で使用したDeploymentとSecretを削除し、環境をクリーンな状態に戻します。

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

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

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

[root@control ~]# kubectl delete -f secret.yaml
secret "nginx-html-secret" deleted from default namespace

Z 参考図書

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

単行本

電子書籍

cgroup v2 の使い方~pids コントローラのインタフェースファイル編

1 はじめに

cgroup(Control Groups)は、複数のプロセスをグループとして管理し、CPU、メモリ、I/O などのシステムリソースをグループ単位で制御・監視する仕組みです。cgroup では、プロセス管理を行うコア・インタフェースファイルと、リソース制御を行うコントローラ・インタフェースファイルを利用して、設定や状態確認を行います。

種類 ファイル名 役割
コア・インタフェースファイル cgroup. というプレフィックスから始まるファイル プロセスの所属管理(cgroup.procs)や、子 cgroup に割り当てるコントローラの制御(cgroup.subtree_control)など、cgroup 自体の共通管理を行う
コントローラ・インタフェースファイル cpu. や memory. など、制御対象のリソース名から始まるファイル CPU 使用率の制限やメモリ使用量の上限設定など、特定のリソースの監視・制御を行う

本ブログでは、コントローラ・インタフェースファイルのうち、プロセス数を制御する pids コントローラのインタフェースファイルについて説明します。

コア・インタフェースファイルについては、以下の記事を参照してください。

hana-shin.hatenablog.com

2 検証環境

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

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

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

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

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

2.2 ノードのリソース

3.5GBのメモリを割り当てています

[root@server ~]# free -h
               total        used        free      shared  buff/cache   available
Mem:           3.5Gi       522Mi       3.0Gi       9.1Mi       195Mi       3.0Gi
Swap:          1.0Gi          0B       1.0Gi

4コアのCPU(4 vCPU) を搭載しています。

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

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

3 pids コントローラのインタフェースファイル

test という名前の cgroup を作成します。

[root@server ~]# mkdir -p /sys/fs/cgroup/test

cgroup を作成すると、そのディレクトリ配下にコア・インタフェースファイル(cgroup.procs など)が自動的に生成されます。コア・インタフェースファイルについては、以下の記事を参照してください。

hana-shin.hatenablog.com

一方、cpu.* や pids.* などのコントローラ・インタフェースファイルは、無条件に生成されるわけではなく、親 cgroup の cgroup.subtree_control でそのコントローラが有効化されている場合にのみ生成されます。ルート(/sys/fs/cgroup)の cgroup.subtree_control を確認すると、pids コントローラがあらかじめ有効になっていることが分かります。

[root@server ~]# cat /sys/fs/cgroup/cgroup.subtree_control
cpu memory pids

今回作成した test cgroup はこのルートの直下に作成しているため、特別な操作をしなくても pids コントローラが有効な状態で引き継がれ、pids.* のコントローラ・インタフェースファイルが自動的に生成されます。

pids コントローラでは、プロセス数の確認や上限設定を行うために、以下のインタフェースファイルを使用します。

[root@server ~]# ls /sys/fs/cgroup/test/pids.*
/sys/fs/cgroup/test/pids.current  /sys/fs/cgroup/test/pids.events.local  /sys/fs/cgroup/test/pids.peak
/sys/fs/cgroup/test/pids.events   /sys/fs/cgroup/test/pids.max
ファイル名 役割
pids.current cgroup に所属しているプロセスの数を表す
pids.max cgroup 内で作成可能なプロセス数の上限を設定する。「max」と書き込むと無制限になる
pids.peak cgroup で過去に記録したプロセス数の最大値を表す
pids.events pids.max に達したことにより、新しいプロセスの作成が拒否された回数などのイベント情報を表す
pids.events.local 現在の cgroup 自身で発生したイベントを表す。子 cgroup で発生したイベントは含まれない

この章では、pids.current、pids.max、pids.peak、pids.events の4つのファイルを実際に操作しながら、それぞれの挙動を確認していきます。

3.1 実験

cgroup 内のプロセス数が上限(pids.max)に達すると、新しいプロセスを一切起動できなくなります。これは、状態確認用の cat や ls といった基本コマンドすら実行できなくなることを意味します。そのため、今回は制限の影響を受けない「確認用の別ターミナル」を同時に開いて実験を進めます。本記事では、root のターミナルと区別しやすいように確認用のターミナルは一般ユーザ(user1)でログインして使用します。以降は、操作用として root でログインしたターミナルと、状態確認用として一般ユーザ(user1)でログインしたターミナルの 2 つを使用します。

[user1@server ~]$

確認用ターミナルから、test cgroup に所属するプロセス数を確認します。まだ test cgroup にプロセスを移動していないため、test cgroup に所属するプロセス数(pids.current)の値は 0 です。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.current
0

test cgroup に移動する bash プロセスの PID を確認します。ここでは、bash プロセスの PID が 2563 であることが分かります。

[root@server ~]# echo $$
2563

実験の最後に、bash プロセスを元の cgroup に戻すため、現在 bash が所属している cgroup を確認します。ここでは、user.slice/user-0.slice/session-1.scope に所属していることが分かります。

[root@server ~]# cat /proc/$$/cgroup
0::/user.slice/user-0.slice/session-1.scope

bash プロセスを test cgroup に移動します。$$ は現在実行中の bash の PID を表しており、cgroup.procs に書き込むことで、bash プロセスを test cgroup に移動させることができます。

[root@server ~]# echo $$ > /sys/fs/cgroup/test/cgroup.procs

bashプロセスが所属するcgroupを確認すると、test cgroupに移動したことがわかります。

[root@server ~]# cat /proc/$$/cgroup
0::/test

bash プロセスがtest cgroup に移動したため、pids.current の値が 0 から 1 に変化したことが分かります。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.current
1

ここで、test cgroup に所属できる最大プロセス数を 3 に設定してみます。

[root@server ~]# echo 3 > /sys/fs/cgroup/test/pids.max

test cgroup に所属する bash プロセスから sleep プロセスを起動します。親プロセスである bash が test cgroup に所属しているため、子プロセスである sleep も自動的に test cgroup に所属します。

[root@server ~]# sleep 600 &
[1] 2606

プロセス数(pids.current)を確認すると、bash プロセスと sleep プロセスの合計 2 個になっていることが分かります。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.current
2

pkill コマンドを実行して sleep プロセスを終了します。

[root@server ~]# pkill sleep
[1]+  Terminated              sleep 600

sleep プロセスを終了したため、test cgroup に所属するプロセスが 2 個から 1 個に減少したことが確認できます。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.current
1

pids.peak は、その cgroup に所属していたプロセス数が過去に到達した最大値を表します。この実験では、bash、sleep、および sleep を終了するために起動した pkill プロセスが同時に存在したため、最大値は 3 となっています。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.peak
3

再度、test cgroup に所属する bash プロセスからsleepプロセスを2つ起動します。

[root@server ~]# sleep 600&
[1] 2616
[root@server ~]# sleep 800&
[2] 2617

プロセスの親子関係を確認します。別のターミナルで確認することで、test cgroup に新たなプロセスを作成せずに状態を確認できます。2 つの sleep プロセスは、いずれも test cgroup に所属する bash プロセス(PID: 2563)から起動していることが確認できます。

[user1@server ~]$ ps -C sleep -o comm,pid,ppid
COMMAND             PID    PPID
sleep              2616    2563
sleep              2617    2563

ps コマンドの詳しい使い方は、以下のページをご覧ください。

hana-shin.hatenablog.com

この時点でのプロセス数(pids.current)は、上限の 3 になります。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.current
3

test cgroupに所属する最大のプロセス数は 3 のままであることがわかります。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.peak
3

新たに sleep を1つ起動します。しかし、test cgroup に移動した bash プロセスと、bash から起動した2個の sleep プロセスの合計3個のプロセスが、すでに test cgroup に存在しています。そのため、新たに sleep を起動することができず、以下のエラーメッセージが出力されています。

[root@server ~]# sleep 900&
-bash: fork: retry: リソースが一時的に利用できません
-bash: fork: retry: リソースが一時的に利用できません
-bash: fork: retry: リソースが一時的に利用できません
^C-bash: fork: システムコール割り込み

pids.events を確認します。max 7 は、pids.max に設定した上限に達したため、新しいプロセスの作成が拒否された回数を表しています。今回の環境では、sleep 900 & を起動しようとした際に、bash が内部的に複数回 fork() を再試行したため、1 回のコマンド実行であっても拒否回数が 7 回として記録されています。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.events
max 7

次に、test cgroup に所属する bash プロセスから ls コマンドを実行してみます。この時点で test cgroup には、bash プロセスと 2 個の sleep プロセスの合計 3 個のプロセスがすでに存在しています。pids.max に設定した上限は 3 のため、新たに ls プロセスを生成しようとすると上限を超えてしまいます。そのため、bash は fork() に失敗し、以下のエラーメッセージが出力されます。

[root@server ~]# ls
-bash: fork: retry: リソースが一時的に利用できません
-bash: fork: retry: リソースが一時的に利用できません
-bash: fork: retry: リソースが一時的に利用できません
^C-bash: fork: システムコール割り込み

pids.events は、pids.max に設定した上限に達したことにより、新しいプロセスの作成が拒否された回数を記録するファイルです。本実験では、sleep や ls の起動に失敗した回数が max に累積されていることが確認できます。

[user1@server ~]$ cat /sys/fs/cgroup/test/pids.events
max 10

3.2 後始末

root で実行していたターミナルは、test cgroup のプロセス数が上限に達しているため、新たなコマンドを実行できません。そのため、まずは確認用のターミナル(user1)から sudo pkill sleep コマンドを実行して sleep プロセスを終了させ、test cgroup にプロセス数の空きを作ります。

[user1@server ~]$ sudo pkill sleep

sleep プロセスが終了したため、test cgroup に所属しているプロセスは bash プロセス(PID: 2563)のみ(計1個)となり、root のターミナルでも再びコマンドが実行できるようになります。

[user1@server ~]$ cat /sys/fs/cgroup/test/cgroup.procs
2563

実験前に確認しておいた元の cgroup に bash プロセスを戻します。これにより、bash プロセスは test cgroup から抜け、もとのセッション cgroup に再所属します。

[root@server ~]# echo $$ > /sys/fs/cgroup/user.slice/user-0.slice/session-1.scope/cgroup.procs

bash プロセスが所属する cgroup を確認すると、元の user.slice/user-0.slice/session-1.scope に戻っていることが確認できます。

[root@server ~]# cat /proc/$$/cgroup
0::/user.slice/user-0.slice/session-1.scope

【NUMA】CPUとメモリの配置がパフォーマンスに与える影響を実験で確かめる

1 はじめに

NUMA(Non-Uniform Memory Access)は、CPUとメモリを複数のノードに分割して構成するアーキテクチャです。

従来のUMA(Uniform Memory Access)では、すべてのCPUからメモリへのアクセス時間はほぼ均一でした。しかし、CPUコア数やメモリ容量の増加に伴い、単一の共有メモリを複数のCPUで利用する方式では、メモリ帯域や拡張性に限界が生じるようになりました。

そこで、CPUの近くにメモリを配置し、それらを1つのノードとして扱うNUMAアーキテクチャが採用されるようになりました。NUMAでは、CPUが同じノード内のメモリへアクセスする場合(ローカルアクセス)は高速ですが、別のノードのメモリへアクセスする場合(リモートアクセス)はノード間の接続を経由するため、一般的にレイテンシが増加します。

近年のLinuxでは、NUMA Automatic BalancingによってCPUとメモリの配置が自動的に最適化されるため、通常の運用ではNUMAを意識する場面は多くありません。しかし、性能問題の調査やアプリケーションのチューニングを行う際には、ローカルアクセスとリモートアクセスの違いや、NUMAがシステム性能に与える影響を理解しておくことが重要です。

本記事では、numactl や stress-ng、numastat などのツールを用いて、CPU とメモリの割り当てを意図的に制御しながら簡単な実験を行い、ローカルアクセスとリモートアクセスで動作や性能がどのように変化するかを確認します。

2 検証環境

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

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

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

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

[root@server ~]# uname -r
6.12.0-211.22.1.el10_2.x86_64

2.2 ノードのリソース

68GiBのメモリを搭載しています。

[root@server ~]# free -h
               total        used        free      shared  buff/cache   available
Mem:            68Gi        14Gi        53Gi        49Mi       1.8Gi        54Gi
Swap:          1.0Gi       1.1Mi       1.0Gi

2.3 検証環境におけるNUMA構成

lscpu の実行結果から、このサーバは 1 つの CPU ソケット を搭載していることが分かります。一方で、NUMA ノード数は 2 となっているため、このシステムは 1 ソケット内に 2 つの NUMA ノードを持つ構成(NUMA ドメインが 2 つ) になっています。また、論理 CPU は合計 32 個 存在し、そのうち NUMA ノード 0 には CPU 0-3、8-11、16-19、24-27 が、NUMA ノード 1 には CPU 4-7、12-15、20-23、28-31 が割り当てられています。NUMA システムでは、各 NUMA ノードがそれぞれローカルメモリを持っており、同じ NUMA ノード内の CPU がローカルメモリへアクセスする場合は高速です。一方、別の NUMA ノードに接続されたメモリへアクセスする場合は、ノード間の接続を経由するためアクセスレイテンシがわずかに増加します。

[root@server ~]# lscpu | egrep 'CPU\(s\)|Socket|NUMA'
CPU(s):                                  32
On-line CPU(s) list:                     0-31
Socket(s):                               1
CPU(s) scaling MHz:                      32%
NUMA node(s):                            2
NUMA node0 CPU(s):                       0-3,8-11,16-19,24-27
NUMA node1 CPU(s):                       4-7,12-15,20-23,28-31

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

また、numactl --hardware コマンドを実行すると、NUMA ノードごとの CPU やメモリの割り当て状況を確認できます。

[root@server ~]# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 8 9 10 11 16 17 18 19 24 25 26 27
node 0 size: 38262 MB
node 0 free: 33387 MB
node 1 cpus: 4 5 6 7 12 13 14 15 20 21 22 23 28 29 30 31
node 1 size: 32248 MB
node 1 free: 30255 MB
node distances:
node     0    1
   0:   10   11
   1:   11   10
  • Node 0 には論理 CPU 0,1,2,3,8,9,10,11,16,17,18,19,24,25,26,27 が所属していること
  • Node 1 には論理 CPU 4,5,6,7,12,13,14,15,20,21,22,23,28,29,30,31 が所属していること
  • Node 0 のメモリサイズは約 37GB、Node 1 のメモリサイズは約 32GB であること
  • node distancesは、同じノード内へのアクセス(0→0、1→1)が 10、別ノードへのアクセス(0→1、1→0)が 11 と、リモートアクセスの方がわずかに値が大きくなっています。距離の絶対値そのものに意味があるわけではなく、「同一ノードのメモリアクセスより、別ノードのメモリアクセスの方がコストが高い」という相対関係を示したものです。


ここまでの情報をまとめると、構成は次のように図示できます。本環境は 1 ソケット構成で、32 個の論理 CPU とメモリが 2 つの NUMA ノードに分割されています。

+----------------------------------------------+
|                 Socket 0                     |
|                                              |
| +------------------+ +------------------+    |
| |   NUMA Node 0    | |   NUMA Node 1    |    |
| |                  | |                  |    |
| | CPUs             | | CPUs             |    |
| | 0-3              | | 4-7              |    |
| | 8-11             | | 12-15            |    |
| | 16-19            | | 20-23            |    |
| | 24-27            | | 28-31            |    |
| |                  | |                  |    |
| | Node 0 Memory    | | Node 1 Memory    |    |
| +------------------+ +------------------+    |
|                                              |
+----------------------------------------------+

3 numactlコマンドの使い方

3.1 CPUとメモリを同じNUMAノード(Node 0)に割り当てる

numactl で Node 0 の CPU を使うように指定(--cpunodebind=0)し、メモリも Node 0 から割り当てるように指定(--membind=0)した上で、stress-ng で10GB のメモリを確保してそのまま待機(3600秒)します。

[root@server ~]# numactl --cpunodebind=0 --membind=0  stress-ng -k --vm 1 --vm-bytes 10G --vm-hang 0 -q&
[1] 6832

stress-ng コマンドの詳しい使い方は、以下のページをご覧ください。

hana-shin.hatenablog.com

実行後、ps コマンドで stress-ng プロセスの状態を確認します。
PSR 列の値から、表示されているすべての stress-ng プロセスが Node 0 に属する論理 CPU 上で動作していることがわかります。この環境では PSR に表示されている 26、16、1 いずれの論理 CPU も Node 0 に属しているため、stress-ng のプロセスはすべて Node 0 上で実行されています。

[root@server ~]#  ps -C stress-ng -o comm,pid,ppid,psr,%mem,%cpu,wchan
COMMAND             PID    PPID PSR %MEM %CPU WCHAN
stress-ng          6832    5607  26  0.0  0.0 do_wait
stress-ng          6833    6832  16  0.0  0.0 do_wait
stress-ng          6834    6833   1 14.5  3.3 hrtimer_nanosleep

ps コマンドの詳しい使い方は、以下のページをご覧ください。

hana-shin.hatenablog.com


続いて numastat コマンドでメモリの割り当て状況を確認します。--membind=0 を指定したため、約 10GB のメモリのほぼすべてが Node 0 に割り当てられていることが確認できます。この例では、CPU とメモリの両方が Node 0 に配置されており、同一 NUMA ノード内で処理が完結しています。

[root@server ~]# numastat 6834

Per-node process memory usage (in MBs) for PID 6834 (stress-ng)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.04            0.00            0.04
Stack                        0.03            0.00            0.03
Private                  10241.64            0.53        10242.17
----------------  --------------- --------------- ---------------
Total                    10241.71            0.53        10242.25

確認が終わったらstress-ngを終了します。

[root@server ~]# pkill stress-ng
[root@server ~]#
[1]+  終了                  numactl --cpunodebind=0 --membind=0 stress-ng -k --vm 1 --vm-bytes 10G --vm-hang 0 -q
[root@server ~]#

3.2 CPUとメモリを同じNUMAノード(Node 1)に割り当てる

今度は、numactl で Node 1 の CPU を使うように指定(--cpunodebind=1)し、メモリも Node 1 から割り当てるように指定(--membind=1)した上で、stress-ng で約 20GB のメモリ負荷を発生させます。

[root@server ~]# numactl --cpunodebind=1 --membind=1 stress-ng -k --vm 1 --vm-bytes 20G --vm-hang 0 -q&
[1] 6809

実行後、ps コマンドで stress-ng プロセスの状態を確認します。
PSR 列の値から、表示されているすべての stress-ng プロセスが Node 1 に属する論理 CPU 上で動作していることがわかります。この環境では PSR に表示されている 12、29、14 いずれの論理 CPU も Node 1 に属しているため、stress-ng のプロセスはすべて Node 1 上で実行されています。

[root@server ~]# ps -C stress-ng -o comm,pid,ppid,psr,%mem,%cpu,wchan
COMMAND             PID    PPID PSR %MEM %CPU WCHAN
stress-ng          6809    5607  12  0.0  0.0 do_wait
stress-ng          6810    6809  29  0.0  0.0 do_wait
stress-ng          6811    6810  14 29.0  2.9 hrtimer_nanosleep

続いて numastat コマンドでメモリの割り当て状況を確認します。--membind=1 を指定したため、約 20GB のメモリのほぼすべてが Node 1 に割り当てられていることがわかります。この例でも、CPU とメモリが同じ Node 1 に配置されているため、ローカルメモリアクセスで処理が行われます。

[root@server ~]# numastat 6811

Per-node process memory usage (in MBs) for PID 6811 (stress-ng)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.04            0.04
Stack                        0.00            0.03            0.03
Private                      0.56        20481.61        20482.17
----------------  --------------- --------------- ---------------
Total                        0.56        20481.68        20482.25

3.3 CPUとメモリを異なるNUMAノードに割り当てる

numactl で CPU は Node 0(--cpunodebind=0)、メモリは Node 1 (--membind=1)を使用するように指定した上で、stress-ng で約 30GB のメモリ負荷を発生させます。

[root@server ~]# numactl --cpunodebind=0 --membind=1  stress-ng -k --vm 1 --vm-bytes 30G --vm-hang 0 -q&
[1] 6845

PSR 列の値から、表示されているすべての stress-ng プロセスが Node 0 に属する論理 CPU 上で動作していることがわかります。この環境では PSR に表示されている 0、1、2 いずれの論理 CPU も Node 0 に属しているため、stress-ng のプロセスはすべて Node 0 上で実行されています。

[root@server ~]#  ps -C stress-ng -o comm,pid,ppid,psr,%mem,%cpu,wchan
COMMAND             PID    PPID PSR %MEM %CPU WCHAN
stress-ng          6845    5607   0  0.0  0.0 do_wait
stress-ng          6846    6845   1  0.0  0.0 do_wait
stress-ng          6847    6846   2 43.5  3.2 hrtimer_nanosleep

続いて numastat コマンドでメモリの割り当て状況を確認します。--membind=1 を指定しているため、約 30GB のメモリはほぼすべて Node 1 に割り当てられています。つまり、このプロセス群は Node 0 の CPU で実行されながら、Node 1 に配置されたメモリへアクセスしている状態です。このように CPU とメモリが異なるノードにある場合、ノード間の通信が発生するため、パフォーマンスに影響が出る場合があります。

[root@server ~]# numastat 6847

Per-node process memory usage (in MBs) for PID 6847 (stress-ng)
                           Node 0          Node 1           Total
                  --------------- --------------- ---------------
Huge                         0.00            0.00            0.00
Heap                         0.00            0.04            0.04
Stack                        0.00            0.03            0.03
Private                      0.55        30721.60        30722.15
----------------  --------------- --------------- ---------------
Total                        0.55        30721.67        30722.23

4 ローカルメモリアクセスとリモートメモリアクセスの性能比較

ここでは、CPU とメモリを同じ NUMA ノードに配置した場合(ローカルアクセス)と、異なる NUMA ノードに配置した場合(リモートアクセス)で、メモリ書き込み処理の実行時間を比較します。CPU は Node 0 に固定し、メモリのみを Node 0 または Node 1 に割り当てて計測を行います。

4.1 1回目

1回目の計測結果です。ローカルアクセスは2.408秒、リモートアクセスは2.714秒でした。

(1) ローカルアクセスの場合
CPUとメモリの両方をNode 0に割り当てて計測します。
なお、stress-ng コマンドの --vm-method オプションに write64nt を使用します。これは、CPUキャッシュをバイパスする Non-Temporal Store 命令を用いて、メモリへ順次書き込みを行う方式です。キャッシュを介さず、DRAMへ直接データを書き込みます。詳細については、stress-ng の man ページを参照してください。

[root@server ~]# time numactl --cpunodebind=0 --membind=0 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 10000
stress-ng: info:  [7584] defaulting to a 1 day run per stressor
stress-ng: info:  [7584] dispatching hogs: 1 vm
stress-ng: info:  [7585] vm: using 30G per stressor instance (total 30G of 64.03G available memory)
stress-ng: info:  [7584] skipped: 0
stress-ng: info:  [7584] passed: 1: vm (1)
stress-ng: info:  [7584] failed: 0
stress-ng: info:  [7584] metrics untrustworthy: 0
stress-ng: info:  [7584] successful run completed in 2.37 secs

real    0m2.408s
user    0m0.469s
sys     0m1.931s

(2) リモートアクセスの場合
CPUはNode 0、メモリはNode 1に割り当てて計測します。

[root@server ~]# time numactl --cpunodebind=0 --membind=1 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 10000
stress-ng: info:  [7587] defaulting to a 1 day run per stressor
stress-ng: info:  [7587] dispatching hogs: 1 vm
stress-ng: info:  [7588] vm: using 30G per stressor instance (total 30G of 63.70G available memory)
stress-ng: info:  [7587] skipped: 0
stress-ng: info:  [7587] passed: 1: vm (1)
stress-ng: info:  [7587] failed: 0
stress-ng: info:  [7587] metrics untrustworthy: 0
stress-ng: info:  [7587] successful run completed in 2.68 secs

real    0m2.714s
user    0m0.534s
sys     0m2.173s

4.2 2回目

2回目の計測です。ローカルアクセスが1.745秒、リモートアクセスが1.888秒でした。

(1) ローカルアクセスの場合
CPUとメモリの両方をNode 0に割り当てて計測します。

[root@server ~]# time numactl --cpunodebind=0 --membind=0 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 10000
stress-ng: info:  [7592] defaulting to a 1 day run per stressor
stress-ng: info:  [7592] dispatching hogs: 1 vm
stress-ng: info:  [7593] vm: using 30G per stressor instance (total 30G of 63.65G available memory)
stress-ng: info:  [7592] skipped: 0
stress-ng: info:  [7592] passed: 1: vm (1)
stress-ng: info:  [7592] failed: 0
stress-ng: info:  [7592] metrics untrustworthy: 0
stress-ng: info:  [7592] successful run completed in 1.71 sec

real    0m1.745s
user    0m1.193s
sys     0m0.548s

(2) リモートアクセスの場合
CPUはNode 0、メモリはNode 1に割り当てて計測します。

[root@server ~]# time numactl --cpunodebind=0 --membind=1 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --
vm-ops 10000
stress-ng: info:  [7595] defaulting to a 1 day run per stressor
stress-ng: info:  [7595] dispatching hogs: 1 vm
stress-ng: info:  [7596] vm: using 30G per stressor instance (total 30G of 63.37G available memory)
stress-ng: info:  [7595] skipped: 0
stress-ng: info:  [7595] passed: 1: vm (1)
stress-ng: info:  [7595] failed: 0
stress-ng: info:  [7595] metrics untrustworthy: 0
stress-ng: info:  [7595] successful run completed in 1.85 sec

real    0m1.888s
user    0m1.225s
sys     0m0.659s

4.3 3回目

3回目の計測です。ローカルアクセスが1.744秒、リモートアクセスが2.019秒でした。

(1) ローカルアクセスの場合

[root@server ~]# time numactl --cpunodebind=0 --membind=0 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 10000
stress-ng: info:  [7598] defaulting to a 1 day run per stressor
stress-ng: info:  [7598] dispatching hogs: 1 vm
stress-ng: info:  [7599] vm: using 30G per stressor instance (total 30G of 63.12G available memory)
stress-ng: info:  [7598] skipped: 0
stress-ng: info:  [7598] passed: 1: vm (1)
stress-ng: info:  [7598] failed: 0
stress-ng: info:  [7598] metrics untrustworthy: 0
stress-ng: info:  [7598] successful run completed in 1.71 sec

real    0m1.744s
user    0m1.080s
sys     0m0.660s

(2) リモートアクセスの場合

[root@server ~]# time numactl --cpunodebind=0 --membind=1 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 10000
stress-ng: info:  [7601] defaulting to a 1 day run per stressor
stress-ng: info:  [7601] dispatching hogs: 1 vm
stress-ng: info:  [7602] vm: using 30G per stressor instance (total 30G of 62.81G available memory)
stress-ng: info:  [7601] skipped: 0
stress-ng: info:  [7601] passed: 1: vm (1)
stress-ng: info:  [7601] failed: 0
stress-ng: info:  [7601] metrics untrustworthy: 0
stress-ng: info:  [7601] successful run completed in 1.98 sec

real    0m2.019s
user    0m1.309s
sys     0m0.705s

4.4 結果まとめ

3回の平均値を計算すると、以下のようになります。

アクセス形態 1回目 2回目 3回目
ローカルアクセス(CPU=Node0, Memory=Node0) 2.408秒 1.745秒 1.744秒
リモートアクセス(CPU=Node0, Memory=Node1) 2.714秒 1.888秒 2.019秒

3回の平均で比較すると、リモートアクセスはローカルアクセスより約12%遅い結果となりました。CPUとメモリを同じNUMAノードに配置することで、メモリアクセスのレイテンシを抑えられることが実測で確認できました。

アクセス形態 平均
ローカルアクセス 1.966秒
リモートアクセス 2.207秒

なお、今回の検証環境は1ソケット構成ですが、2ソケット以上の環境ではソケット間インターコネクトを経由してメモリアクセスを行うため、ローカルアクセスとリモートアクセスの性能差がさらに大きくなる可能性があります。

5 numastat によるローカルアクセスとリモートアクセスの確認

これまでの検証では、numastat を使用してプロセス単位のメモリ配置を確認しました。本節では、numastat が表示するシステム全体の統計情報を利用して、ローカルメモリアクセスとリモートメモリアクセスの発生状況を確認します。これらの統計情報は累積値として記録されるため、実験の前後で numastat の結果を取得し、その差分を比較します。

numastatコマンドで表示される統計情報の意味です。

項目 意味
numa_hit 要求したノードからメモリ確保に成功した回数
numa_miss 要求したノードで確保できず別ノードから確保した回数
numa_foreign 他ノードが本ノードのメモリを利用した回数
interleave_hit インターリーブポリシーで割り当てられた回数
local_node CPUが所属するNUMAノードのメモリへアクセスした回数
other_node CPUが所属するNUMAノード以外のメモリへアクセスした回数

5.1 ローカルメモリアクセスの確認

実験前の NUMA 統計情報を保存します。

[root@server ~]# numastat > before 

続いて、CPU とメモリをともに Node 0 に割り当てて、ローカルメモリアクセスが発生するように stress-ng を実行します。

[root@server ~]# time numactl --cpunodebind=0 --membind=0 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 100

実行後の NUMA 統計情報を取得します。

[root@server ~]# numastat > after

実験前後の差分を比較します。

[root@server ~]# sdiff before  after
                           node0           node1                                           node0           node1
numa_hit                22562613        10209485              | numa_hit                22605783        10209650
numa_miss                      0           98174                numa_miss                      0           98174
numa_foreign               98174               0                numa_foreign               98174               0
interleave_hit              2373            2607                interleave_hit              2373            2607
local_node              22551066         9883314              | local_node              22594236         9883479
other_node                 11547          424345                other_node                 11547          424345

差分を確認すると、主に node0 の numa_hit と local_node が増加していることが分かります。これは、Node 0 上で実行されたプロセスが Node 0 のメモリを利用して処理を行っていることを示しています。

  • numa_hit

node0: 22605783 − 22562613 = 43,170 増加
node1: 10209650 − 10209485 = 165 増加

  • local_node

node0: 22594236 − 22551066 = 43,170 増加
node1: 9883479 − 9883314 = 165 増加

  • numa_miss, numa_foreign, interleave_hit, other_node は変化なし

5.2 リモートメモリアクセスの確認

次に、CPUは Node 0、メモリは Node 1 に割り当て、あえてリモートメモリアクセスを発生させます。

実験前の NUMA 統計情報を保存します。

[root@server ~]# numastat > before 

今度は --membind=1 を指定し、メモリを強制的に Node 1 へ割り当てて実行します。

[root@server ~]# time numactl --cpunodebind=0 --membind=1 stress-ng --vm 1 --vm-bytes 30G --vm-method write64nt --vm-keep --vm-ops 100

実行後の NUMA 統計情報を取得します。

[root@server ~]# numastat > after

実験前後の NUMA 統計情報を比較します。
差分を確認すると、Node 1 側の numa_hit が大きく増加していることから、メモリが Node 1 に割り当てられたことが分かります。また、other_node の値も大きく増加しており、Node 0 上の CPU が Node 1 のメモリへアクセスしていることが確認できます。

[root@server ~]# sdiff before  after
                           node0           node1                                           node0           node1
numa_hit                22605861        10210529              | numa_hit                22605862        10222677
numa_miss                      0           98174                numa_miss                      0           98174
numa_foreign               98174               0                numa_foreign               98174               0
interleave_hit              2373            2607                interleave_hit              2373            2607
local_node              22594314         9884358              | local_node              22594315         9884589
other_node                 11547          424345              | other_node                 11547          436262
  • numa_hit

node0: 1 増加
node1: 10222677-10210529 = 12,148 増加

  • local_node

node0: 1 増加
node1: 231 増加

  • other_node

node1: 436262-424345 = 11,917 増加

other_node は CPU が所属する NUMA ノード以外のメモリへアクセスした回数を示します。
今回の検証では CPU を Node 0 に固定し、メモリを Node 1 に割り当てています。そのため、Node 0 上の CPU が Node 1 のメモリへアクセスするリモートメモリアクセスが多数発生し、その結果として other_node が大きく増加したことが分かります。

5.3 結果のまとめ

ここまでの実験で、以下のことが検証できました。

  • ローカルアクセス時: local_node が増加する
  • リモートアクセス時: other_node が増加する

numastat のシステム統計をチェックすれば、サーバー全体でどちらのアクセスが多く発生しているかが一目で判断できます。
前節の性能測定では、リモートアクセスが発生すると実行時間が約12%も長くなる ことが分かりました。NUMA環境のパフォーマンスを最大限引き出すためには、CPUとメモリを同じノードにバランスよく配置し、リモートアクセス(other_node)をいかに減らすかが重要になります。

6 NUMA 配置の自動最適化

ここまでの章では、numactl を使用して CPU とメモリの割り当てを手動で制御し、ローカルアクセスとリモートアクセスの振る舞いを確認しました。しかし、実運用環境において、プロセスごとに CPU やメモリの配置を手動で調整し続けることは現実的ではありません。そのため Linux には、NUMA 配置を自動的に最適化するための仕組みがいくつか用意されています。代表的なものが以下の 2 つです。

仕組み 概要
numad ユーザー空間で動作するデーモン。システムの CPU 使用率やメモリ使用状況を監視し、CPU アフィニティやメモリ配置を調整することで、ローカルメモリアクセスの割合を高めることを目的とした仕組み
NUMA Automatic Balancing(Auto NUMA Balancing) Linux カーネルがメモリアクセスパターンを監視し、必要に応じてメモリページやタスクの配置を最適化する機能。ユーザー空間のデーモンに依存せず、カーネルレベルで動作します。Red Hat Enterprise Linux 7 以降ではデフォルトで有効となっています

docs.redhat.com

本ブログの検証環境では、numad は無効になっています。

[root@server ~]# systemctl is-enabled numad.service
disabled

一方、NUMA Automatic Balancing は有効になっていますので、本検証環境では NUMA 配置の最適化が自動的に行われる設定になっていることがわかります。

[root@server ~]# cat /proc/sys/kernel/numa_balancing
1

Z 参考記事

kernel-internals.org

cgroup v2 の使い方~インタフェースファイルの役割とプロセス・スレッド制御の実験

1 cgroupとは?

cgroup(Control Groups)は、複数のプロセスをひとつのグループとして管理し、グループ単位で CPU、メモリ、I/O などのシステムリソースの使用量を制御・監視する仕組みです。cgroup v2 は AlmaLinux 8 で導入され、AlmaLinux 9 以降ではデフォルトの cgroup として利用されています。

本記事で使用している環境では、cgroup v2 が使用されています。以下のコマンドを実行することで、使用されている cgroup のバージョンが v2 であることを確認できます。

[root@server ~]# mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,seclabel,nsdelegate,memory_recursiveprot)

参考記事
gihyo.jp

2 検証環境

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

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

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

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

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

2.2 ノードのリソース

4GBのメモリを割り当てています。

[root@server ~]# free -h
               total        used        free      shared  buff/cache   available
Mem:           3.5Gi       522Mi       3.0Gi       9.1Mi       195Mi       3.0Gi
Swap:          1.0Gi          0B       1.0Gi

4コアのCPU(4 vCPU) を搭載しています。

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

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

3 インタフェースファイル

/sys/fs/cgroup/ 配下に任意の名前のディレクトリを作成することで、新しいcgroup を作成することができます。以下は、test という名前の cgroup を作成するときの例です。

[root@server ~]# mkdir /sys/fs/cgroup/test

作成したディレクトリ配下に自動生成されるファイルは、以下の 2 種類に分類されています。これらのファイルを介して cgroup の設定や状態確認を行います。

種類 ファイル名 役割
コア・インタフェースファイル cgroup. というプレフィックスから始まる プロセスの所属管理(cgroup.procs)や、子 cgroup へ割り当てるコントローラの制御(cgroup.subtree_control)など、cgroup そのものの共通管理を行う
コントローラ・インタフェースファイル cpu. や memory. など、制御対象のリソース名から始まる CPU の使用率制限やメモリの上限設定など、特定のリソースを制限・制御する


(1) コア・インタフェースファイル
コア・インタフェースファイルは、以下のように cgroup. で始まるファイルです。

[root@control ~]# ls /sys/fs/cgroup/test/cgroup.*
/sys/fs/cgroup/test/cgroup.controllers      /sys/fs/cgroup/test/cgroup.procs
/sys/fs/cgroup/test/cgroup.events           /sys/fs/cgroup/test/cgroup.stat
/sys/fs/cgroup/test/cgroup.freeze           /sys/fs/cgroup/test/cgroup.subtree_control
/sys/fs/cgroup/test/cgroup.kill             /sys/fs/cgroup/test/cgroup.threads
/sys/fs/cgroup/test/cgroup.max.depth        /sys/fs/cgroup/test/cgroup.type
/sys/fs/cgroup/test/cgroup.max.descendants
種類 概要
cgroup.controllers この cgroup で利用可能なコントローラ(cpu、memoryなど)の一覧を表示します
cgroup.procs この cgroup に所属するプロセスの PID を表示します。PID を書き込むと、そのプロセスを元の cgroup から、この cgroup に移動します
cgroup.threads この cgroup に所属するスレッドの TID を表示します
cgroup.max.depth 作成できる子 cgroup の最大階層数を表示または設定します
cgroup.max.descendants 作成できる子孫 cgroup の最大数を表示または設定します
cgroup.subtree_control 子 cgroup に対して有効化するコントローラを設定します
cgroup.stat cgroup の状態情報(子 cgroup 数など)を表示します

(2) コントローラ・インタフェースファイル

各コントローラには専用のインタフェースファイルが用意されており、コントローラ名で始まるファイルとして配置されています。たとえば、cpuset コントローラには以下のようなインタフェースファイルがあります。

[root@control ~]# ls /sys/fs/cgroup/test/cpuset.*
/sys/fs/cgroup/test/cpuset.cpus
/sys/fs/cgroup/test/cpuset.cpus.effective
/sys/fs/cgroup/test/cpuset.cpus.exclusive
/sys/fs/cgroup/test/cpuset.cpus.exclusive.effective
/sys/fs/cgroup/test/cpuset.cpus.partition
/sys/fs/cgroup/test/cpuset.mems
/sys/fs/cgroup/test/cpuset.mems.effective

4 親プロセス移動時の子プロセスの cgroup 所属

本章では、cgroup v2 におけるプロセスの移動と cgroup 継承の動作を検証します。具体的には、以下の2点を確認することを本章の目的とします。

(1) 既存の子プロセスへの影響:
bash から sleep プロセスを起動した状態で、親プロセスである bash を別の cgroup へ移動した際、すでに起動している子プロセス(sleep)の所属 cgroup が変化するかどうか?

(2) 新規子プロセスへの継承:
bash を別の cgroup へ移動した後、その bash から新たにプロセスを起動した際、子プロセスが親プロセス(bash)の所属する cgroup を継承するかどうか?

4.1 動作確認

/sys/fs/cgroup/ 配下に test という名前の cgroup を作成します。

[root@control ~]# mkdir /sys/fs/cgroup/test

現在のシェル自身のPIDを確認します。$$ は現在のシェル自身の PID(プロセスID)を格納している特殊変数です。以降の作業でこのプロセスを移動させるため、あらかじめ PID を確認しておきます。

[root@control ~]# echo $$
46324

現在の bash プロセスがどの cgroup に所属しているかを確認します。通常、ログイン直後はシステム(systemd)がログインセッションに対して自動的に割り当てた cgroup に所属しています。

[root@control ~]# cat /proc/$$/cgroup
0::/user.slice/user-0.slice/session-3.scope

sleep 600 を現在の bash の子プロセスとしてバックグラウンドで起動します。ここでは PID 62585 が割り当てられました。

[root@control ~]# sleep 600 &
[1] 62585

sleep 600 の親プロセスが bash (sleepの親プロセスのPID が46324)であることを確認します。

[root@control ~]# ps -C sleep -o comm,pid,ppid,args
COMMAND             PID    PPID COMMAND
sleep             62585   46324 sleep 600

bash の子プロセスとして起動した sleep 600 も、この時点では親プロセス(bash)と同じ初期 cgroup に自動的に所属していることを確認します。

[root@control ~]# cat /proc/62585/cgroup
0::/user.slice/user-0.slice/session-3.scope

bash 自身の PID を、先ほど作成した test ディレクトリ内の cgroup.procs に書き込みます。cgroup v2 では、この操作によって指定したプロセス単体のみが移動します。

[root@control ~]# echo $$ > /sys/fs/cgroup/test/cgroup.procs

bash が test cgroup へ移動されたことを確認します。

[root@control ~]# cat /proc/$$/cgroup
0::/test

親プロセス(bash)を test へ移動させても、移動前から存在していた子プロセス sleep 600 の所属 cgroup は変化していないことを確認します。cgroup v2 では、親を移動しても既存の子プロセスは追従しないことが分かります。

[root@control ~]# cat /proc/62585/cgroup
0::/user.slice/user-0.slice/session-3.scope

test cgroup の cgroup.procs を確認します。移動した bash(46324)のほかに、一時的に実行された cat コマンド自身(PID: 62961)など、その瞬間 test 配下で動いているプロセスのみが表示されます。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.procs
46324
62961

test cgroup に移動した bash から、新たに sleep 800 をバックグラウンドで起動します。

[root@control ~]# sleep 800&
[2] 63103

ps コマンドで確認すると、移動前に起動した sleep 600 と、移動後に起動した sleep 800 の双方が、同じ bash(PID: 46324)を親に持っていることが分かります。

[root@control ~]# ps -C sleep -o comm,pid,ppid,args
COMMAND             PID    PPID COMMAND
sleep             62585   46324 sleep 600
sleep             63103   46324 sleep 800

test cgroup のプロセス一覧を確認します。bash に加え、新たに起動した sleep 800 がこの cgroup に含まれていることが確認できます(63509 は cat コマンドの PID です)。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.procs
46324
63103
63509

bash 自身が現在も test cgroup に所属し続けていることを確認します。

[root@control ~]# cat /proc/$$/cgroup
0::/test

移動後の bash から起動した sleep 800 の cgroup を確認します。親プロセスの現在の cgroup をしっかりと継承し、test に所属していることが確認できました。移動前に起動した sleep 600 の結果と対比することで、cgroup 伝播のタイミングが明確になります。

[root@control ~]# cat /proc/63103/cgroup
0::/test

4.2 後始末

実験用に変更した環境を元の状態に戻します。(cgroup は内部にプロセスが残っていると削除できないため、プロセスを終了させるか元の cgroup に戻す必要があります)

[root@control ~]# jobs
[1]-  終了                  sleep 600
[2]+  終了                  sleep 800

実験用に起動した 2 つの sleep プロセス(ジョブ番号 %1 と %2)に終了シグナルを送り、プロセスを破棄します。

[root@control ~]# kill %1 %2

作成した test cgroup を削除できるようにするため、bash プロセス(PID: 46324)を実験開始前の初期 cgroup(session-3.scope)へ移動します。

[root@control ~]# echo 46324 > /sys/fs/cgroup/user.slice/user-0.slice/session-3.scope/cgroup.procs

bash の所属が最初のログインセッションの cgroup に正しく戻っていることを確認します。

[root@control ~]# cat /proc/$$/cgroup
0::/user.slice/user-0.slice/session-3.scope
[root@control ~]# rmdir /sys/fs/cgroup/test

4.3 実験の結果まとめ

  • cgroup の移動は指定したプロセス単体にのみ適用される:親プロセスを別の cgroup へ移動させても、すでに起動している子プロセスの cgroup は変わりません
  • 新規プロセスは生成時の親プロセスの cgroup を継承する:親プロセスの移動後に、新しく起動した子プロセスは、その時点の親と同じ cgroup を自動的に引き継ぎます。

5 コア・インタフェースファイルの使い方

5.1 cgroup.controllers

cgroup.controllers は、その cgroup において、利用可能なすべてのリソースコントローラ(CPUやメモリなどを制御する機能)の一覧を確認するための、読み取り専用のファイルです。

test という名前の cgroup ディレクトリを作成します。

[root@control ~]# mkdir /sys/fs/cgroup/test

作成した test ディレクトリ直下にある cgroup.controllers の中身を cat コマンドで表示してみます。出力結果を見ると、cpuset や cpu など、この cgroup で利用可能なリソースコントローラを確認することができます。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.controllers
cpuset cpu io memory hugetlb pids rdma misc dmem

5.2 cgroup.kill

cgroup 内のすべてのプロセスを一括で強制終了(kill)するためのファイルです。「1」を書き込むことで、その cgroup に所属するすべてのプロセスに SIGKILL シグナルが送信されます。

4章の実験では、親プロセスを別の cgroup へ移動しても、すでに起動している子プロセスの cgroup は変化しない一方、新たに起動した子プロセスは親プロセスの cgroup を継承することを確認しました。そのため、本実験では最初にシェル(bash)を test cgroup へ移動してから sleep プロセスを起動します。これにより、起動した sleep プロセスも自動的に test cgroup に所属するため、後続の cgroup.kill の動作を同一 cgroup 内の複数プロセスに対して確認できます。

現在のシェルの PID を確認します。

[root@server ~]# echo $$
2657

現在のシェルを test cgroup に移動します。

[root@control ~]# echo $$ > /sys/fs/cgroup/test/cgroup.procs

シェルが test cgroup に所属していることがを確認します。

[root@control ~]# cat /proc/$$/cgroup
0::/test

続いて、シェルから 2 つの sleep プロセスを起動します。

[root@server ~]# sleep 600&
[1] 2734
[root@server ~]# sleep 800&
[2] 2735

起動した sleep プロセスを確認すると、いずれも現在のシェルを親プロセスとして実行されていることが分かります。

[root@server ~]# ps -C sleep -o comm,pid,ppid,args
COMMAND             PID    PPID COMMAND
sleep              2734    2657 sleep 600
sleep              2735    2657 sleep 800

PID 2734 の sleep プロセスの /proc/[PID]/cgroup を確認すると、test cgroup に所属していることが分かります。

[root@server ~]# cat /proc/2734/cgroup
0::/test

同様に、PID 2735 の sleep プロセスについても、test cgroup に所属していることが確認できます。

[root@server ~]# cat /proc/2735/cgroup
0::/test

別のターミナルから test cgroup に所属するプロセスを確認します。ここには、test cgroup に所属するシェルおよび sleep プロセスの PID が表示されています。

[root@server ~]# cat /sys/fs/cgroup/test/cgroup.procs
2657
2734
2735
2746

cgroup.kill に 1 を書き込むと、test cgroup に所属するすべてのプロセスへ SIGKILL が送信されます。

[root@control ~]# echo 1 > /sys/fs/cgroup/test/cgroup.kill
Connection to 192.168.122.2 closed.

別のターミナルから test cgroup の状態を確認します。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.procs
[root@control ~]#

5.3 cgroup.max.depth

cgroup の配下に、最大で何階層まで子 cgroup(子ディレクトリ)を作成できるかを制限するファイルです。デフォルトは「max(無制限)」です。値を 1 に指定すると1階層下まで、2 に指定すると2階層下の cgroup ディレクトリまで作成できるようになります。ここでは、値を 2 に指定して、階層数の制限がどのように行われるかを確認してみます。

まずは、今回の実験の起点となる test という名前の cgroup ディレクトリを作成します。

[root@control ~]# mkdir /sys/fs/cgroup/test

test ディレクトリ直下の cgroup.max.depth ファイルに 2 を書き込みます。これにより、test から数えて2階層下までしか新しい cgroup(ディレクトリ)を作れないように制限が課されます。

[root@control ~]# echo 2 > /sys/fs/cgroup/test/cgroup.max.depth

1階層目(test/test1)の作成:制限内(2階層まで)なので成功します

[root@control ~]# mkdir /sys/fs/cgroup/test/test1
[root@control ~]#

2階層目(test/test1/test2)の作成:制限内(2階層まで)なので成功します

[root@control ~]# mkdir /sys/fs/cgroup/test/test1/test2
[root@control ~]#

3階層目(test/test1/test2/test3)の作成:設定した「2階層」を超えるため、エラーになります

[root@control ~]# mkdir /sys/fs/cgroup/test/test1/test2/test3
mkdir: ディレクトリ `/sys/fs/cgroup/test/test1/test2/test3' を作成できません: リソース が一時的に利用できません

5.4 cgroup.subtree_control

cgroup.subtree_control は、「子 cgroup(下位の階層)に対して、どのリソースコントローラ(CPUやメモリ制限など)の利用を許可するか」を制御するためのファイルです。
親ディレクトリでの有効化・無効化によって、子ディレクトリ内のファイルがどのように変化するかを確認してみます。

親 cgroup の test ディレクトリを作成します。

[root@control ~]# mkdir /sys/fs/cgroup/test

親の下に、リソース制限の対象となる子 cgroup の test1 ディレクトリを作成します。

[root@control ~]# mkdir /sys/fs/cgroup/test/test1

初期状態で、親(test)が子(test1)に対して何かコントローラを有効化しているかを確認します。まだ何も書き込んでいないため、出力は空(設定なし)です。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.subtree_control
[root@control ~]#

親が何も有効化していない状態で、子(test1)の中にどのようなファイルがあるかを確認します。結果を見ると、cgroup. で始まるコア・インタフェースファイルと、自動的に生成される cpu.stat のみであり、リソース制限用のファイル(cpu.max など)は存在しません。

[root@control ~]# ls /sys/fs/cgroup/test/test1/
cgroup.controllers  cgroup.freeze  cgroup.max.depth        cgroup.procs  cgroup.subtree_control  cgroup.type  cpu.stat.local
cgroup.events       cgroup.kill    cgroup.max.descendants  cgroup.stat   cgroup.threads          cpu.stat

親(test)の cgroup.subtree_control に +cpu を書き込みます。これにより、子(test1)に対して CPU の制限機能の利用を許可します。

[root@control ~]# echo "+cpu" > /sys/fs/cgroup/test/cgroup.subtree_control

もう一度、子(test1)のディレクトリ内を確認します。先ほどは存在しなかった、CPU を実際に制限するための cpu.max や cpu.weight といった「コントローラ・インタフェースファイル」が自動的に生成されていることが分かります。

[root@control ~]# ls /sys/fs/cgroup/test/test1/
cgroup.controllers  cgroup.kill             cgroup.procs            cgroup.threads  cpu.max        cpu.stat.local  cpu.weight
cgroup.events       cgroup.max.depth        cgroup.stat             cgroup.type     cpu.max.burst  cpu.uclamp.max  cpu.weight.nice
cgroup.freeze       cgroup.max.descendants  cgroup.subtree_control  cpu.idle        cpu.stat       cpu.uclamp.min

今度は逆に、親(test)の cgroup.subtree_control に -cpu を書き込み、子(test1)への CPU 制限機能の許可を取り消します。

[root@control ~]# echo "-cpu" > /sys/fs/cgroup/test/cgroup.subtree_control
[root@control ~] #

最後に、再度子(test1)のディレクトリ内を確認します。親側で機能をオフにしたため、先ほどまで出現していた cpu.max などの制限用ファイルが削除され、初期状態(コアファイルと統計ファイルのみ)に戻っていることが確認できます。

[root@control ~]# ls /sys/fs/cgroup/test/test1/
cgroup.controllers  cgroup.freeze  cgroup.max.depth        cgroup.procs  cgroup.subtree_control  cgroup.type  cpu.stat.local
cgroup.events       cgroup.kill    cgroup.max.descendants  cgroup.stat   cgroup.threads          cpu.stat

5.5 cgroup.type

本節では、cgroup.type が domain および threaded の cgroup における動作の違いを確認します。domain 型ではプロセス単位で、threaded 型ではスレッド単位で cgroup を管理できます。本実験では CPU コントローラを利用し、それぞれの挙動を確認します。

5.5.1 事前準備

検証で使用するプログラムを作成します。学習用のサンプルプログラムのため、エラー処理は省略しています。

[root@control ~]# cat tp.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>

void* cpu_burner(void* arg) {
    int thread_num = *(int*)arg;

    pid_t tid = syscall(SYS_gettid);

    printf("[Thread %d] 起動完了 - スレッドID(TID): %d (CPUに負荷をかけています...)\n", thread_num, tid);
    free(arg);

    while (1) {
        // ここで無限ループさせることで、CPUコアを使い切ります
    }

    return NULL;
}

int main(int argc, char* argv[]) {

    int num_threads = atoi(argv[1]);

    printf("========================================\n");
    printf("メインプロセス(親PID): %d\n", getpid());
    printf("%d 個のスレッドを生成します...\n", num_threads);
    printf("========================================\n");

    pthread_t* threads = malloc(sizeof(pthread_t) * num_threads);

    for (int i = 0; i < num_threads; i++) {
        int* thread_arg = malloc(sizeof(int));
        *thread_arg = i + 1;

        pthread_create(&threads[i], NULL, cpu_burner, thread_arg);
    }

    printf("すべてのスレッドが起動しました。Ctrl+C で終了します。\n\n");

    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);
    }

    free(threads);
    return 0;
}

コンパイルします。

[root@control ~]# gcc -Wall -pthread -o tp tp.c

tpの動作確認をします。tpの引数は起動するスレッド数を指定します。

[root@control ~]# ./tp 2 &
[1] 194279
[root@control ~]# ========================================
メインプロセス(親PID): 194279
2 個のスレッドを生成します...
========================================
すべてのスレッドが起動しました。Ctrl+C で終了します。

[Thread 2] 起動完了 - スレッドID(TID): 194281 (CPUに負荷をかけています...)
[Thread 1] 起動完了 - スレッドID(TID): 194280 (CPUに負荷をかけています...)

ps コマンドを実行すると、メインスレッド(TID: 194279)は futex_do_wait で待機していることが分かります。これは、メインスレッドが子スレッドの終了を待っている状態であることを示しています。一方、子スレッド(TID: 194280、194281)は CPU を集中的に使用しており、それぞれ約 90% の CPU 使用率となっていることが確認できます。

[root@control ~]# ps -C tp -L -o comm,pid,tid,psr,%cpu,wchan
COMMAND             PID     TID PSR %CPU WCHAN
tp               194279  194279   0  0.0 futex_do_wait
tp               194279  194280   2 88.7 -
tp               194279  194281   0 87.3 -

TPを終了します。

[root@control ~]# pkill tp
[root@control ~]#
[1]+  Terminated              ./tp 2
5.5.2 プロセス制御(domainモードでの検証)

実験用cgroupを作成します。

[root@control ~]# mkdir -p /sys/fs/cgroup/test/domain

作成直後のcgroupのタイプは、デフォルトである通常モード(domain)であることを確認します。

[root@control ~]# cat /sys/fs/cgroup/test/domain/cgroup.type
domain

検証用のプログラム tp を4スレッド指定でバックグラウンド起動します(親PID: 112376)。

[root@control ~]# ./tp 4&
[1] 112375
[root@control ~]# ========================================
メインプロセス(親PID): 112376
4 個のスレッドを生成します...
========================================
[Thread 1] 起動完了 - スレッドID(TID): 112377 (CPUに負荷をかけています...)
[Thread 2] 起動完了 - スレッドID(TID): 112378 (CPUに負荷をかけています...)
[Thread 3] 起動完了 - スレッドID(TID): 112379 (CPUに負荷をかけています...)
[Thread 4] 起動完了 - スレッドID(TID): 112380 (CPUに負荷をかけています...)
すべてのスレッドが起動しました。Ctrl+C で終了します。

まだcgroupによる制限がかかっていないため、各高負荷スレッド(TID: 112377〜112380)がそれぞれ約80%のCPUを消費しています。

[root@control ~]# ps -C tp -L -o comm,pid,tid,ppid,%cpu
COMMAND             PID     TID    PPID %CPU
tp               112376  112376       1  0.0
tp               112376  112377       1 78.9
tp               112376  112378       1 79.0
tp               112376  112379       1 78.8
tp               112376  112380       1 80.3

プロセスのPID(112376)を cgroup.procs に書き込み、プロセス全体を domain グループへ移動します。

[root@control ~]# echo  112376 > /sys/fs/cgroup/test/domain/cgroup.procs

domain グループに所属しているPIDを確認します。

[root@control ~]# cat /sys/fs/cgroup/test/domain/cgroup.procs
112376

グループに移動しただけでは、まだCPUの上限制限を設定していないため、各スレッドの使用率に変化はありません。

[root@control ~]# ps -C tp -L -o comm,pid,tid,ppid,%cpu
COMMAND             PID     TID    PPID %CPU
tp               112376  112376       1  0.0
tp               112376  112377       1 78.2
tp               112376  112378       1 78.3
tp               112376  112379       1 78.4
tp               112376  112380       1 78.4

親階層で cpu コントローラを有効化します。

[root@control ~]# echo "+cpu" > /sys/fs/cgroup/test/cgroup.subtree_contro

domain グループ全体のCPU上限を 50%(0.5コア分) に制限します。

[root@control ~]# echo "50000 100000" > /sys/fs/cgroup/test/domain/cpu.max

CPU 制限設定後、4本の高負荷スレッドのCPU使用率がそれぞれ約14~15%程度まで低下していることが確認できます。この結果から、cpu.max に設定した上限はプロセス単位ではなく cgroup 全体に対して適用され、その枠内で各スレッドがCPU時間を共有していることが分かります。つまり、プロセスを domain cgroup に所属させた場合、同じプロセスに属する全スレッドが cgroup のCPU制限の対象となります。

[root@control ~]# ps -C tp -L -o comm,pid,tid,ppid,psr,%cpu
COMMAND             PID     TID    PPID PSR %CPU
tp               112376  112376       1   2  0.0
tp               112376  112377       1   3 14.5
tp               112376  112378       1   1 14.5
tp               112376  112379       1   0 14.5
tp               112376  112380       1   2 14.5

(2) あと始末
pkill tp により、tp プロセスおよびそのスレッドを終了します。

[root@control ~]# pkill tp

ps コマンドで確認すると、tp に該当するプロセスおよびスレッドが存在しないことが分かります。

[root@control ~]# ps -C tp -L -o comm,pid,tid,ppid,psr,%cpu
COMMAND             PID     TID    PPID PSR %CPU
[root@control ~]#

続いて、domain cgroup に所属するプロセスがなくなったことを確認します。

[root@control ~]# cat /sys/fs/cgroup/test/domain/cgroup.procs
[root@control ~]#

最後に、実験で作成した cgroup を削除します。cgroup v2 では、プロセスが所属している cgroup や子 cgroup を持つ cgroup は削除できません。今回のように、プロセスが存在せず、子 cgroup も存在しない状態であるため、rmdir による削除が正常に完了します。

[root@control ~]# rmdir /sys/fs/cgroup/test/domain
[root@control ~]# rmdir /sys/fs/cgroup/test
[root@control ~]#
5.5.3 スレッド制御

スレッドモード(threaded)を利用し、同じプロセスに属するスレッドを別々の子cgroupに分散させ、スレッド単位で異なるCPU制限を適用する検証を行います。

[root@control ~]# mkdir -p /sys/fs/cgroup/test/thread1
[root@control ~]# mkdir -p /sys/fs/cgroup/test/thread2

子グループ(thread1, thread2)のタイプを threaded に変更します。

[root@control ~]# echo threaded > /sys/fs/cgroup/test/thread1/cgroup.type
[root@control ~]# echo threaded > /sys/fs/cgroup/test/thread2/cgroup.type

これに伴い、親グループ(test)のタイプが、配下にスレッドモードを持つことを示す domain threaded へと自動的に変形します。

[root@control ~]# cat /sys/fs/cgroup/test/cgroup.type
domain threaded
[root@control ~]# cat /sys/fs/cgroup/test/thread1/cgroup.type
threaded
[root@control ~]# cat /sys/fs/cgroup/test/thread2/cgroup.type
threaded

親階層の cgroup.subtree_control で cpu コントローラを有効化し、子グループへリソースを分配できるようにします。

[root@control ~]# echo "+cpu" > /sys/fs/cgroup/test/cgroup.subtree_control

検証用TPを起動します。

[root@control ~]# ./tp 4&
[1] 201199
========================================
メインプロセス(親PID): 201199
4 個のスレッドを生成します...
========================================
[Thread 1] 起動完了 - スレッドID(TID): 201200 (CPUに負荷をかけています...)
[Thread 3] 起動完了 - スレッドID(TID): 201202 (CPUに負荷をかけています...)
[Thread 2] 起動完了 - スレッドID(TID): 201201 (CPUに負荷をかけています...)
すべてのスレッドが起動しました。Ctrl+C で終了します。

[root@control ~]# [Thread 4] 起動完了 - スレッドID(TID): 201203 (CPUに負荷をかけています...)

[root@control ~]#

4つの高負荷スレッドを持つプロセス(PID: 201199)を起動します。この段階では制限がないため、各スレッド(TID: 201200〜201203)がそれぞれ約78%のCPUを消費しています。

[root@control ~]# ps -C tp -L -o comm,pid,tid,psr,%cpu,wchan
COMMAND             PID     TID PSR %CPU WCHAN
tp               201199  201199   0  0.0 futex_do_wait
tp               201199  201200   3 77.2 -
tp               201199  201201   2 77.9 -
tp               201199  201202   0 78.6 -
tp               201199  201203   2 78.9 -

まず、プロセスの基点となるメインプロセス(PID: 201199)を親グループ(test)に所属させます。

[root@control ~]# echo 201199 > /sys/fs/cgroup/test/cgroup.procs
[root@control ~]# cat /proc/201199/cgroup
0::/test

cgroup.procs はプロセス単位の移動に使用します。一方、threaded cgroup では cgroup.threads を使用することで、同一プロセス内の個々のスレッドを別々の cgroup に配置できます。

[root@control ~]# echo 201200 > /sys/fs/cgroup/test/thread1/cgroup.threads
[root@control ~]# echo 201201 > /sys/fs/cgroup/test/thread1/cgroup.threads
[root@control ~]# echo 201202 > /sys/fs/cgroup/test/thread2/cgroup.threads
[root@control ~]# echo 201203 > /sys/fs/cgroup/test/thread2/cgroup.threads

各タスクの /proc/[TID]/cgroup を確認すると、同じプロセスに属していながら、各スレッドが異なるcgroup階層に割り振られていることが確認できます。

[root@control ~]# cat /proc/201199/cgroup
0::/test
[root@control ~]# cat /proc/201200/cgroup
0::/test/thread1
[root@control ~]# cat /proc/201201/cgroup
0::/test/thread1
[root@control ~]# cat /proc/201202/cgroup
0::/test/thread2
[root@control ~]# cat /proc/201203/cgroup
0::/test/thread2

スレッドを分散させた直後は、まだCPUの上限制限を設定していないため、各スレッドの使用率は高いまま維持されています。

[root@control ~]# ps -C tp -L -o comm,pid,tid,psr,%cpu,wchan
COMMAND             PID     TID PSR %CPU WCHAN
tp               201199  201199   0  0.0 futex_do_wait
tp               201199  201200   3 76.5 -
tp               201199  201201   0 76.2 -
tp               201199  201202   1 77.1 -
tp               201199  201203   1 76.8 -

thread1 グループの上限を 40%、thread2 グループの上限を 20% にそれぞれ個別に制限します。

[root@control ~]# echo "40000 100000" > /sys/fs/cgroup/test/thread1/cpu.max
[root@control ~]# echo "20000 100000" > /sys/fs/cgroup/test/thread2/cpu.max

thread1 に所属するスレッドは約20%ずつ、thread2 に所属するスレッドは約10%ずつの CPU 使用率となっています。これは、それぞれの cgroup に設定した CPU 上限(40%、20%)が同一 cgroup 内のスレッド間で分配された結果です。domain モードではプロセス単位で制御されますが、threaded モードではスレッド単位で異なるリソース制御を適用できることが確認できます。

[root@control ~]# ps -C tp -L -o comm,pid,tid,psr,%cpu,wchan
COMMAND             PID     TID PSR %CPU WCHAN
tp               201199  201199   0  0.0 futex_do_wait
tp               201199  201200   1 21.2 -
tp               201199  201201   3 21.2 -
tp               201199  201202   2 12.0 -
tp               201199  201203   1 12.0 -

Helmコマンド入門〜インストールから基本的な使い方まで〜

1 Helmとは?

HelmはKubernetesのパッケージマネージャです。Linuxのdnfやaptのようにコマンドひとつでアプリケーションをインストールでき、Kubernetesのマニフェス(YAMLファイル)をまとめて管理できます。

Helmで使用する用語について以下に説明します。

用語 意味
チャート アプリケーションを Kubernetes クラスタにデプロイするためのマニフェスト(Deployment、Service、Ingress など)をまとめたパッケージのことです。一般的に、アプリケーションを Kubernetes 上で動作させるには複数のマニフェストを作成・管理する必要がありますが、Helm チャートを利用することで、これらをひとまとめにしてインストールやアップデートを行えます。各マニフェストはテンプレート化されており、テンプレートに埋め込む値はデフォルト値(values.yaml)で定義されています。これらの値を変更することで、レプリカ数やコンテナイメージのバージョンなどを環境に応じてカスタマイズし、異なる設定のマニフェストを生成できます
リリース チャートをクラスタにインストールした際に作成される実体です。各リリースは一意の名前を持ち、同じチャートから異なる名前で複数のリリースを作成でき、それぞれ異なる設定を持つことができます
リポジトリ チャートが公開されている保管場所です。dnfやaptなどのパッケージリポジトリと同じ役割を果たします

Helmのコンポーネントについて以下に説明します。

コンポーネント 意味
Chart.yaml チャートの名前やバージョンなどのメタ情報を記載するファイルです
templates/ディレクトリ マニフェストのテンプレートを格納するディレクトリです
values.yaml テンプレートに適用するデフォルト値を定義するファイルです

2 検証環境

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

2.1 ネットワーク構成

+--- control ---+    +--- worker1 ---+   +--- worker2 ---+
|               |    |               |   |               |
|AlmaLinux 10.2 |    |AlmaLinux 10.2 |   |AlmaLinux 10.2 |
|               |    |               |   |               |
+-------+-------+    +-------+-------+   +-------+-------+
        |.8                  |.9                 |.10
        |                    |                   |
        |                    |                   |
        |   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

3 Helmのインストール方法

helm.sh

公式ページの内容にしたがって、Helmのインストール用スクリプトをダウンロードします。

[root@control ~]# curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4

ダウンロードしたスクリプトを確認します。

[root@control ~]# ls -l get_helm.sh
-rw-r--r--. 1 root root 11929  4月 13 15:55 get_helm.sh

スクリプトに実行権限を付与します。

[root@control ~]# chmod 700 get_helm.sh

スクリプトの実行権を確認します。

[root@control ~]# ls -l get_helm.sh
-rwx------. 1 root root 11929  4月 13 15:55 get_helm.sh

スクリプトを実行して、Helmをインストールします。

[root@control ~]# ./get_helm.sh
Downloading https://get.helm.sh/helm-v4.1.4-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm

インストールされたHelmのバージョンを確認します。

[root@control ~]# helm version --short
v4.1.4+g05fa379

4 Helmコマンドのオプション一覧

helm --help コマンドを実行すると、Helmで使用できるコマンドとオプションの一覧が表示されます。

[root@control ~]# helm --help
The Kubernetes package manager

Common actions for Helm:

- helm search:    search for charts
- helm pull:      download a chart to your local directory to view
- helm install:   upload the chart to Kubernetes
- helm list:      list releases of charts

Environment variables:

| Name                               | Description                                                                                                |
|------------------------------------|------------------------------------------------------------------------------------------------------------|
| $HELM_CACHE_HOME                   | set an alternative location for storing cached files.                                                      |
| $HELM_CONFIG_HOME                  | set an alternative location for storing Helm configuration.                                                |
| $HELM_DATA_HOME                    | set an alternative location for storing Helm data.                                                         |
| $HELM_DEBUG                        | indicate whether or not Helm is running in Debug mode                                                      |
| $HELM_DRIVER                       | set the backend storage driver. Values are: configmap, secret, memory, sql.                                |
| $HELM_DRIVER_SQL_CONNECTION_STRING | set the connection string the SQL storage driver should use.                                               |
| $HELM_MAX_HISTORY                  | set the maximum number of helm release history.                                                            |
| $HELM_NAMESPACE                    | set the namespace used for the helm operations.                                                            |
| $HELM_NO_PLUGINS                   | disable plugins. Set HELM_NO_PLUGINS=1 to disable plugins.                                                 |
| $HELM_PLUGINS                      | set the path to the plugins directory                                                                      |
| $HELM_REGISTRY_CONFIG              | set the path to the registry config file.                                                                  |
| $HELM_REPOSITORY_CACHE             | set the path to the repository cache directory                                                             |
| $HELM_REPOSITORY_CONFIG            | set the path to the repositories file.                                                                     |
| $KUBECONFIG                        | set an alternative Kubernetes configuration file (default "~/.kube/config")                                |
| $HELM_KUBEAPISERVER                | set the Kubernetes API Server Endpoint for authentication                                                  |
| $HELM_KUBECAFILE                   | set the Kubernetes certificate authority file.                                                             |
| $HELM_KUBEASGROUPS                 | set the Groups to use for impersonation using a comma-separated list.                                      |
| $HELM_KUBEASUSER                   | set the Username to impersonate for the operation.                                                         |
| $HELM_KUBECONTEXT                  | set the name of the kubeconfig context.                                                                    |
| $HELM_KUBETOKEN                    | set the Bearer KubeToken used for authentication.                                                          |
| $HELM_KUBEINSECURE_SKIP_TLS_VERIFY | indicate if the Kubernetes API server's certificate validation should be skipped (insecure)                |
| $HELM_KUBETLS_SERVER_NAME          | set the server name used to validate the Kubernetes API server certificate                                 |
| $HELM_BURST_LIMIT                  | set the default burst limit in the case the server contains many CRDs (default 100, -1 to disable)         |
| $HELM_QPS                          | set the Queries Per Second in cases where a high number of calls exceed the option for higher burst values |
| $HELM_COLOR                        | set color output mode. Allowed values: never, always, auto (default: never)                                |
| $NO_COLOR                          | set to any non-empty value to disable all colored output (overrides $HELM_COLOR)                           |

Helm stores cache, configuration, and data based on the following configuration order:

- If a HELM_*_HOME environment variable is set, it will be used
- Otherwise, on systems supporting the XDG base directory specification, the XDG variables will be used
- When no other location is set a default location will be used based on the operating system

By default, the default directories depend on the Operating System. The defaults are listed below:

| Operating System | Cache Path                | Configuration Path             | Data Path               |
|------------------|---------------------------|--------------------------------|-------------------------|
| Linux            | $HOME/.cache/helm         | $HOME/.config/helm             | $HOME/.local/share/helm |
| macOS            | $HOME/Library/Caches/helm | $HOME/Library/Preferences/helm | $HOME/Library/helm      |
| Windows          | %TEMP%\helm               | %APPDATA%\helm                 | %APPDATA%\helm          |

Usage:
  helm [command]

Available Commands:
  completion  generate autocompletion scripts for the specified shell
  create      create a new chart with the given name
  dependency  manage a chart's dependencies
  env         helm client environment information
  get         download extended information of a named release
  help        Help about any command
  history     fetch release history
  install     install a chart
  lint        examine a chart for possible issues
  list        list releases
  package     package a chart directory into a chart archive
  plugin      install, list, or uninstall Helm plugins
  pull        download a chart from a repository and (optionally) unpack it in local directory
  push        push a chart to remote
  registry    login to or logout from a registry
  repo        add, list, remove, update, and index chart repositories
  rollback    roll back a release to a previous revision
  search      search for a keyword in charts
  show        show information of a chart
  status      display the status of the named release
  template    locally render templates
  test        run tests for a release
  uninstall   uninstall a release
  upgrade     upgrade a release
  verify      verify that a chart at the given path has been signed and is valid
  version     print the helm version information

Flags:
      --burst-limit int                 client-side default throttling limit (default 100)
      --color string                    use colored output (never, auto, always) (default "auto")
      --colour string                   use colored output (never, auto, always) (default "auto")
      --content-cache string            path to the directory containing cached content (e.g. charts) (default "/root/.cache/helm/content")
      --debug                           enable verbose output
  -h, --help                            help for helm
      --kube-apiserver string           the address and the port for the Kubernetes API server
      --kube-as-group stringArray       group to impersonate for the operation, this flag can be repeated to specify multiple groups.
      --kube-as-user string             username to impersonate for the operation
      --kube-ca-file string             the certificate authority file for the Kubernetes API server connection
      --kube-context string             name of the kubeconfig context to use
      --kube-insecure-skip-tls-verify   if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure
      --kube-tls-server-name string     server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used
      --kube-token string               bearer token used for authentication
      --kubeconfig string               path to the kubeconfig file
  -n, --namespace string                namespace scope for this request
      --qps float32                     queries per second used when communicating with the Kubernetes API, not including bursting
      --registry-config string          path to the registry config file (default "/root/.config/helm/registry/config.json")
      --repository-cache string         path to the directory containing cached repository indexes (default "/root/.cache/helm/repository")
      --repository-config string        path to the file containing repository names and URLs (default "/root/.config/helm/repositories.yaml")

Use "helm [command] --help" for more information about a command.

5 リポジトリを追加/削除する方法(add/remove)

Helmはインストール直後の状態では、参照するリポジトリが登録されていません。
そのため、Chartを利用するには、helm repo add コマンドでリポジトリを登録する必要があります。

コンポーネント名 概要 リポジトリのURL
Metrics Server PodやNodeのCPU・メモリ使用率を収集。kubectl topやHPAで使用 https://kubernetes-sigs.github.io/metrics-server
Prometheus メトリクスの収集、保存、アラート通知を行う監視プラットフォーム https://prometheus-community.github.io/helm-charts
Grafana Prometheus等で収集したデータをダッシュボードで可視化 https://grafana.github.io/helm-charts
kube-state-metrics リソース(Pod/ReplicaSet等)の状態を監視用にメトリクス化 https://prometheus-community.github.io/helm-charts
Ingress-NGINX L7ロードバランサとして、クラスタ外部からのHTTP/HTTPS通信を制御 https://kubernetes.github.io/ingress-nginx
cert-manager Let's Encrypt等と連携し、TLS証明書の取得・更新を自動化 https://charts.jetstack.io

prometheus という名前でリポジトリを追加します。

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

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

[root@control ~]# helm repo list
NAME            URL
prometheus      https://prometheus-community.github.io/helm-charts

追加したリポジトリは、以下のファイルに保存されます。

[root@control ~]# cat .config/helm/repositories.yaml
apiVersion: ""
generated: "0001-01-01T00:00:00Z"
repositories:
- caFile: ""
  certFile: ""
  insecure_skip_tls_verify: false
  keyFile: ""
  name: prometheus
  pass_credentials_all: false
  password: ""
  url: https://prometheus-community.github.io/helm-charts
  username: ""

helm search repo コマンドを実行すると、指定した prometheus リポジトリに含まれるChart 一覧が表示されます。リポジトリに、kube-prometheus-stack ,prometheus ,prometheus-adapter等のチャートがあることがわかります。

[root@control ~]# helm search repo prometheus
NAME                                                    CHART VERSION   APP VERSION     DESCRIPTION
prometheus/kube-prometheus-stack                        84.1.1          v0.90.1         kube-prometheus-stack collects Kubernetes manif...
prometheus/prometheus                                   29.2.1          v3.11.2         Prometheus is a monitoring system and time seri...
prometheus/prometheus-adapter                           5.3.0           v0.12.0         A Helm chart for k8s prometheus adapter
-snip-

リポジトリを削除します。

[root@control ~]# helm repo remove prometheus
"prometheus" has been removed from your repositories

リポジトリを確認します。リポジトリが削除されたことが確認できます。

[root@control ~]# helm repo list
no repositories to show

6 チャートの中身を確認する方法(show)

helm show コマンドを使用すると、チャートをダウンロードせずに、リポジトリ上のチャートの設定項目や概要をターミナル上で確認できます。インストール前にデフォルト値や設定項目を把握したい場合に使用します。ここでは、prometheusリポジトリのkube-prometheus-stackチャートを例に説明します。

[root@control ~]# helm show values prometheus/kube-prometheus-stack
# Default values for kube-prometheus-stack.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.

## Provide a name in place of kube-prometheus-stack for `app:` labels
##
nameOverride: ""

## Override the deployment namespace
##
namespaceOverride: ""
-snip-

7 チャートをダウンロードする方法(pull)

helm pull コマンドを使用すると、リポジトリのチャートをローカル環境へダウンロードできます。ダウンロードしたチャートはtemplates/やvalues.yamlの内容を直接確認・編集したい場合に使用します。ここでは、prometheusリポジトリに登録されているprometheus-adapter チャートをダウンロードしてみます。

[root@control ~]# helm pull prometheus/prometheus-adapter --untar

ダウンロードおよび解凍が完了すると、prometheus-adapter ディレクトリが作成されます。

[root@control ~]# cd prometheus-adapter/

このディレクトリには、チャートの定義ファイル(Chart.yaml)や設定ファイル(values.yaml)、テンプレート(templates/)などが含まれています。

[root@control prometheus-adapter]# ls -F
Chart.yaml  README.md  ci/  templates/  values.yaml

templates/ディレクトリ内のテンプレートには、{{ .Values.xxx }} という形式で値の埋め込み箇所が定義されており、values.yamlに記載されたデフォルト値がこの箇所に埋め込まれることで、クラスタに適用するマニフェストが生成されます。例えば、values.yamlに listenPort: 6443 と定義されている場合、templates/deployment.yaml内の {{ .Values.listenPort }} に 6443 が埋め込まれます。生成されたマニフェストは、helm install コマンドを実行することでクラスタに適用されます。

[root@control prometheus-adapter]# grep -r listenPort *
templates/deployment.yaml:        - --secure-port={{ .Values.listenPort }}
templates/deployment.yaml:        - containerPort: {{ .Values.listenPort }}
values.yaml:listenPort: 6443

8 チャートをインストール/削除する方法(install /uninstall )

8.1 インストールする方法

helm install コマンドを使用すると、チャートのテンプレートにvalues.yamlの値を埋め込んだマニフェストが生成され、クラスタに適用されます。ここでは、metrics-serverをクラスタにインストールします。

metrics-serverのリポジトリを追加します。

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

追加したリポジトリを確認します。metrics-serverのリポジトリが登録されたことが確認できます。

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

metrics-serverをインストールします。metrics-serverはKubernetesのシステムコンポーネントとして動作するため、-n kube-system を指定してkube-system Namespaceにインストールします。

[root@control ~]# helm install metrics-server metrics-server/metrics-server -n kube-system
NAME: metrics-server
LAST DEPLOYED: Mon Apr 27 10:51:01 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
***********************************************************************
* Metrics Server                                                      *
***********************************************************************
  Chart version: 3.13.0
  App version:   0.8.0
  Image tag:     registry.k8s.io/metrics-server/metrics-server:v0.8.0
***********************************************************************

kube-system Namespaceにインストールされたチャートを確認します。

[root@control ~]# helm list -n kube-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
metrics-server  kube-system     1               2026-04-27 10:51:01.98586347 +0900 JST  deployed        metrics-server-3.13.0   0.8.0

metrics-serverのPodがkube-system Namespaceで稼働していることを確認します。

[root@control ~]# kubectl get pod -n kube-system -o wide
NAME                                     READY   STATUS    RESTARTS        AGE   IP                NODE      NOMINATED NODE   READINESS GATES
calico-kube-controllers-9dff488b-h59b9   1/1     Running   9 (128m ago)    18d   10.244.189.83     worker2   <none>           <none>
calico-node-8xgzc                        1/1     Running   6 (128m ago)    13d   192.168.122.171   worker2   <none>           <none>
calico-node-c8l6h                        1/1     Running   5 (128m ago)    13d   192.168.122.2     control   <none>           <none>
calico-node-snl52                        1/1     Running   6 (129m ago)    13d   192.168.122.139   worker1   <none>           <none>
coredns-7d764666f9-dh9mt                 1/1     Running   12 (128m ago)   24d   10.244.42.205     control   <none>           <none>
coredns-7d764666f9-dxqkd                 1/1     Running   14 (128m ago)   24d   10.244.189.84     worker2   <none>           <none>
etcd-control                             1/1     Running   18 (128m ago)   35d   192.168.122.2     control   <none>           <none>
kube-apiserver-control                   1/1     Running   18 (128m ago)   35d   192.168.122.2     control   <none>           <none>
kube-controller-manager-control          1/1     Running   20 (128m ago)   35d   192.168.122.2     control   <none>           <none>
kube-proxy-cr79f                         1/1     Running   18 (128m ago)   35d   192.168.122.2     control   <none>           <none>
kube-proxy-j5q84                         1/1     Running   24 (128m ago)   35d   192.168.122.171   worker2   <none>           <none>
kube-proxy-kt576                         1/1     Running   20 (129m ago)   35d   192.168.122.139   worker1   <none>           <none>
kube-scheduler-control                   1/1     Running   20 (128m ago)   35d   192.168.122.2     control   <none>           <none>
metrics-server-644bf9cbdb-wv2mm          1/1     Running   1 (129m ago)    22h   10.244.235.182    worker1   <none>           <none>

次に、helm status コマンドを実行してみます。helm status コマンドを使用すると、インストールしたリリースの基本情報(インストール日時・Namespace・状態)と、そのリリースで作成されたリソースの一覧をまとめて確認できます。リソースの確認は kubectl get コマンドでも可能ですが、リソースの種類ごとに個別に実行する必要があります。helm status を使うことで、1つのコマンドで作成されたすべてのリソースを確認できます。

[root@control ~]# helm status metrics-server -n kube-system
NAME: metrics-server
LAST DEPLOYED: Mon Apr 27 11:25:01 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
RESOURCES:
==> v1/ServiceAccount
NAME             AGE
metrics-server   21m

==> v1/ClusterRole
NAME                                      CREATED AT
system:metrics-server-aggregated-reader   2026-04-27T02:25:01Z
system:metrics-server   2026-04-27T02:25:01Z

==> v1/ClusterRoleBinding
NAME                                   ROLE                                AGE
metrics-server:system:auth-delegator   ClusterRole/system:auth-delegator   21m
system:metrics-server   ClusterRole/system:metrics-server   21m

==> v1/RoleBinding
NAME                         ROLE                                             AGE
metrics-server-auth-reader   Role/extension-apiserver-authentication-reader   21m

==> v1/Service
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
metrics-server   ClusterIP   10.103.41.235   <none>        443/TCP   21m

==> v1/Deployment
NAME             READY   UP-TO-DATE   AVAILABLE   AGE
metrics-server   1/1     1            1           21m

==> v1/Pod(related)
NAME                              READY   STATUS    RESTARTS   AGE
metrics-server-644bf9cbdb-wv2mm   1/1     Running   0          21m

==> v1/APIService
NAME                     SERVICE                      AVAILABLE   AGE
v1beta1.metrics.k8s.io   kube-system/metrics-server   True        21m


TEST SUITE: None
NOTES:
***********************************************************************
* Metrics Server                                                      *
***********************************************************************
  Chart version: 3.13.0
  App version:   0.8.0
  Image tag:     registry.k8s.io/metrics-server/metrics-server:v0.8.0
***********************************************************************

metrics-serverのインストールが完了すると、各ノードのメトリクスを収集できるようになります。

[root@control ~]# kubectl top node
NAME      CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)
control   595m         14%      1865Mi          52%
worker1   217m         5%       976Mi           27%
worker2   503m         12%      1156Mi          32%

なお、デフォルトの状態ではkubeletのサーバ証明書の検証エラーにより、メトリクスの収集に失敗する場合があります。その場合は以下のようなエラーが表示されます。対処方法については、【kubeletサーバ証明書の検証エラーの原因と対処方法】(別途公開予定)を参照してください。

[root@control ~]# kubectl top node
error: Metrics API not available

8.2 削除する方法

インストールしたmetrics-serverのチャートを削除します。

[root@control ~]# helm uninstall metrics-server -n kube-system
release "metrics-server" uninstalled

チャートが削除されたことを確認します。metrics-serverのチャートが一覧から消えていることがわかります。

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

9 チャートの設定値を確認する方法(get values)

インストール後に「どの値を変更してインストールしたか」やトラブル発生時に「意図した値が適用されているか」を確認したい場合につかいます。null が表示された場合は、values.yamlのデフォルト値のままインストールされたことを意味します。

[root@control ~]# helm get values metrics-server -n kube-system
USER-SUPPLIED VALUES:
null

10 リリースをアップグレード/ダウグレードする方法(upgrade /rollback)

Helmでは、インストール済みのリリースをアップグレードしたり、問題が発生した場合に以前のバージョンに戻す(ロールバック)ことができます。この章では helm upgrade と helm rollback コマンドの使い方を解説します。

10.1 アップグレードする方法

helm upgrade コマンドを使うことで、インストール済みのリリースをアップグレードできます。--set オプションを指定することで、チャートの設定値を変更しながらアップグレードすることも可能です。ここでは、replicas を2に変更しながらアップグレードする例を紹介します

[root@control ~]# helm upgrade metrics-server metrics-server/metrics-server -n kube-system --set replicas=2
Release "metrics-server" has been upgraded. Happy Helming!
NAME: metrics-server
LAST DEPLOYED: Mon Apr 27 12:08:19 2026
NAMESPACE: kube-system
STATUS: deployed
REVISION: 2
DESCRIPTION: Upgrade complete
TEST SUITE: None
NOTES:
***********************************************************************
* Metrics Server                                                      *
***********************************************************************
  Chart version: 3.13.0
  App version:   0.8.0
  Image tag:     registry.k8s.io/metrics-server/metrics-server:v0.8.0
***********************************************************************

helm get values で変更した値が反映されていることを確認します。

[root@control ~]# helm get values metrics-server -n kube-system
USER-SUPPLIED VALUES:
replicas: 2

replicasを2に変更したことで、metrics-serverのPodが2つ稼働していることを確認します。

[root@control ~]# kubectl get pods -n kube-system
NAME                                     READY   STATUS    RESTARTS         AGE
calico-kube-controllers-9dff488b-h59b9   1/1     Running   8 (4h37m ago)    18d
calico-node-8xgzc                        1/1     Running   5 (4h37m ago)    12d
calico-node-c8l6h                        1/1     Running   4 (4h37m ago)    12d
calico-node-snl52                        1/1     Running   5 (4h37m ago)    12d
coredns-7d764666f9-dh9mt                 1/1     Running   11 (4h37m ago)   23d
coredns-7d764666f9-dxqkd                 1/1     Running   13 (4h37m ago)   23d
etcd-control                             1/1     Running   17 (4h37m ago)   35d
kube-apiserver-control                   1/1     Running   17 (4h37m ago)   35d
kube-controller-manager-control          1/1     Running   19 (4h37m ago)   35d
kube-proxy-cr79f                         1/1     Running   17 (4h37m ago)   35d
kube-proxy-j5q84                         1/1     Running   23 (4h37m ago)   35d
kube-proxy-kt576                         1/1     Running   19 (4h37m ago)   35d
kube-scheduler-control                   1/1     Running   19 (4h37m ago)   35d
metrics-server-644bf9cbdb-fl5hf          1/1     Running   0                24m
metrics-server-644bf9cbdb-wv2mm          1/1     Running   0                67m

helm upgrade を実行するとREVISIONが1から2に増加していることを確認します。REVISIONはアップグレードするたびに増加します。

[root@control ~]# helm list -n kube-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
metrics-server  kube-system     2               2026-04-27 12:08:19.010714666 +0900 JST deployed        metrics-server-3.13.0   0.8.0

10.2 ダウグレードする方法

helm rollback コマンドを使うことで、リリースを以前のリビジョンに戻すことができます。

まず helm list で現在のリビジョンが 2 であることを確認します。

[root@control ~]# helm list -n kube-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
metrics-server  kube-system     2               2026-04-27 12:08:19.010714666 +0900 JST deployed        metrics-server-3.13.0   0.8.0

次に helm history でリビジョンの履歴を確認します。リビジョン 1 が初回インストール、リビジョン 2 がアップグレード済みであることがわかります。

[root@control ~]# helm history metrics-server -n kube-system
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Mon Apr 27 11:25:01 2026        superseded      metrics-server-3.13.0   0.8.0           Install complete
2               Mon Apr 27 12:08:19 2026        deployed        metrics-server-3.13.0   0.8.0           Upgrade complete

helm rollback metrics-server 1 を実行することで、リビジョン 1 の状態にロールバックします。

[root@control ~]# helm rollback metrics-server 1 -n kube-system
Rollback was a success! Happy Helming!

ロールバック後に helm list を確認すると、リビジョンが 3 に増えていることがわかります。これはロールバック自体も新しいリビジョンとして記録されるためです。

[root@control ~]# helm list -n kube-system
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
metrics-server  kube-system     3               2026-04-27 12:55:58.565636905 +0900 JST deployed        metrics-server-3.13.0   0.8.0

helm get values で設定値を確認すると null となっており、10.1でアップグレード時に変更した --set の設定が元に戻っていることが確認できます。

[root@control ~]# helm get values metrics-server -n kube-system
USER-SUPPLIED VALUES:
null

最後に kubectl get pods で metrics-server のPod数を確認します。アップグレード前と同じPodが1個の状態に戻っていることがわかります。

[root@control ~]# kubectl get pods -n kube-system
NAME                                     READY   STATUS    RESTARTS        AGE
calico-kube-controllers-9dff488b-h59b9   1/1     Running   8 (5h1m ago)    18d
calico-node-8xgzc                        1/1     Running   5 (5h1m ago)    12d
calico-node-c8l6h                        1/1     Running   4 (5h1m ago)    12d
calico-node-snl52                        1/1     Running   5 (5h1m ago)    12d
coredns-7d764666f9-dh9mt                 1/1     Running   11 (5h1m ago)   23d
coredns-7d764666f9-dxqkd                 1/1     Running   13 (5h1m ago)   23d
etcd-control                             1/1     Running   17 (5h1m ago)   35d
kube-apiserver-control                   1/1     Running   17 (5h1m ago)   35d
kube-controller-manager-control          1/1     Running   19 (5h1m ago)   35d
kube-proxy-cr79f                         1/1     Running   17 (5h1m ago)   35d
kube-proxy-j5q84                         1/1     Running   23 (5h1m ago)   35d
kube-proxy-kt576                         1/1     Running   19 (5h1m ago)   35d
kube-scheduler-control                   1/1     Running   19 (5h1m ago)   35d
metrics-server-644bf9cbdb-wv2mm          1/1     Running   0               91m

11 同一チャートから複数リリースの作成

ここでは学習用として、Bitnami が提供する nginx チャートを使用します。リリースごとに異なる設定を適用するため、バージョン1のリリース(nginx-v1)は Pod 数を2、バージョン2のリリース(nginx-v2)は Pod 数を4に設定してインストールします。

11.1 動作確認

まず、Bitnami の Helm リポジトリを登録し最新のチャート情報を取得します。

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

登録された Helm リポジトリを確認します。bitnami リポジトリが登録されていることを確認できます。

[root@control ~]# helm repo list
NAME    URL
bitnami https://charts.bitnami.com/bitnami

登録したリポジトリから最新のチャート情報を取得します。これにより、Helm はリポジトリで公開されている最新のチャート情報を利用できるようになります。

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

続いて、nginx チャートで Pod 数を制御するパラメータを確認します。replicaCount のデフォルト値は 1 であることが分かります。

[root@control ~]# helm show values bitnami/nginx|grep replicaCount
## @param replicaCount Number of NGINX replicas to deploy
replicaCount: 1

リリース名を nginx-v1 とし、Pod 数を 2 に設定してインストールします。

[root@control ~]# helm install nginx-v1 bitnami/nginx --set replicaCount=2
NAME: nginx-v1
LAST DEPLOYED: Wed Jun 10 14:26:45 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 25.0.3
APP VERSION: 1.31.1
-snip-

インストール後に Pod を確認すると、nginx-v1 に対応する Pod が 2 つ作成されていることを確認できます。

[root@control ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
nginx-v1-86d5ddd99d-4rxv8   1/1     Running   0          4m43s
nginx-v1-86d5ddd99d-wpcp8   1/1     Running   0          4m43s

次に、同じ nginx チャートを使用して、別のリリース名 nginx-v2 でインストールします。このとき、Pod 数は 4 に設定します。

[root@control ~]# helm install nginx-v2 bitnami/nginx --set replicaCount=4
NAME: nginx-v2
LAST DEPLOYED: Wed Jun 10 14:32:04 2026
NAMESPACE: default
STATUS: deployed
REVISION: 1
DESCRIPTION: Install complete
TEST SUITE: None
NOTES:
CHART NAME: nginx
CHART VERSION: 25.0.3
APP VERSION: 1.31.1
-snip-

Pod を確認すると、nginx-v1 の Pod が 2 つ存在したまま、新たに nginx-v2 の Pod が 4 つ作成されていることが分かります。

[root@control ~]# kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
nginx-v1-86d5ddd99d-4rxv8   1/1     Running   0          5m43s
nginx-v1-86d5ddd99d-wpcp8   1/1     Running   0          5m43s
nginx-v2-7547f7f6d9-5f85p   1/1     Running   0          25s
nginx-v2-7547f7f6d9-6prft   1/1     Running   0          25s
nginx-v2-7547f7f6d9-pvdxl   1/1     Running   0          25s
nginx-v2-7547f7f6d9-vnl9p   1/1     Running   0          25s

Deployment を確認すると、同じチャートから作成された 2 つのリリースが、それぞれ異なる設定で独立して管理されていることが分かります。

[root@control ~]# kubectl get deployment
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
nginx-v1   2/2     2            2           7m6s
nginx-v2   4/4     4            4           108s

この結果から、Helm では同一のチャートを複数回インストールでき、リリースごとに異なる設定値を適用して独立した環境を構築できることが分かります。例えば、開発環境と検証環境で異なる設定を適用したい場合や、同じアプリケーションを複数の構成で動作させたい場合に活用できます。

11.2 あと始末

検証で作成したリリースを削除します。まず、helm list コマンドでインストールされているリリースを確認します。

[root@control ~]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART           APP VERSION
nginx-v1        default         1               2026-06-10 14:26:45.735057555 +0900 JST deployed        nginx-25.0.3    1.31.1
nginx-v2        default         1               2026-06-10 14:32:04.619119644 +0900 JST deployed        nginx-25.0.3    1.31.1

helm uninstall コマンドを実行して、nginx-v2 と nginx-v1 のリリースを削除します。リリースを削除すると、Helm が管理している Deployment、ReplicaSet、Pod、Service などの関連リソースも併せて削除されます。

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

Pod を確認すると、リリースに関連するリソースが削除されていることを確認できます。

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

この結果から、Helm では helm uninstall コマンドを実行することで、リリースに関連付けられた Kubernetes リソースをまとめて削除できることが分かります。


Z 参考図書

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

単行本

電子書籍

Deployment の基本操作を確認する

1 Deploymentとは

Deployment とは、Pod の作成・更新・管理を行う Kubernetes のオブジェクトです。YAML マニフェストで定義した「あるべき状態」と実際の状態を比較し、その差分を自動的に調整することで、指定した状態を維持します。通常、Kubernetes では Pod を直接作成するのではなく、Deployment を使用して Pod を管理します。本記事では、以下の機能について、実機を使用して動作をします。

機能 概要
レプリカ数の維持 指定したPod数を常に維持する
ローリングアップデート サービスを停止せずに、Pod を順番に新しいバージョンへ更新する
ロールバック 更新に失敗した場合、以前の状態に戻す
スケーリング 負荷や運用状況に応じて Pod の数を増減する

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 Deploymentの基本操作

3.1 マニフェストの適用方法(apply)

(1) マニフェストの作成
クラスタに適用するマニフェストを作成します。

[root@control ~]# vi nginx.yaml
[root@control ~]# cat nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 2
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.23
        ports:
        - containerPort: 80

(2) マニフェストの適用(apply)
以下のコマンドを実行して、クラスタにマニフェストを適用します。

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

kubectl get deployment コマンドを実行すると、nginx-deployment という名前の Deployment が作成されていることが確認できます。また、READY が 2/2 となっていることから、Deployment が管理する 2 つの Pod が Ready 状態で正常に稼働していることがわかります。IMAGES からは、Deployment に設定されたコンテナイメージが nginx:1.23 であることが確認できます。また、SELECTOR からは、Deployment が app=nginx というラベルを持つ Pod を管理対象としていることがわかります。
これらの情報から、マニフェストで定義した内容どおりに Deployment が作成され、関連する Pod も正しく起動していることを確認できます。

[root@control ~]# kubectl get deployment -o wide
NAME               READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS   IMAGES       SELECTOR
nginx-deployment   2/2     2            2           9s    nginx        nginx:1.23   app=nginx

kubectl get pods コマンドを実行すると、Pod が 2 つ起動していることを確認できます。READY がどちらも 1/1 となっていることから、それぞれの Pod 内のコンテナが正常に起動し、Ready 状態であることがわかります。また、STATUS が Running となっていることから、Pod が正常に稼働していることも確認できます。

[root@control ~]# kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-5c58774b6-45k87   1/1     Running   0          26s   10.244.189.110   worker2   <none>           <none>
nginx-deployment-5c58774b6-pjvm9   1/1     Running   0          26s   10.244.235.137   worker1   <none>           <none>

3.2 マニフェストの削除方法(delete)

クラスタに適用したマニフェストを削除します。

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

nginx-deploymentという名前のDeploymentが削除されたことがわかります。

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

マニフェストを削除すると、Podも削除されたことがわかります。

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

4 Deploymentの機能確認

4.1 レプリカ数の維持

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

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

作成されたPodを確認します。2つのPodがそれぞれ異なるノード(worker1、worker2)で起動していることがわかります。

[root@control ~]# kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-5c58774b6-6j48r   1/1     Running   0          6s    10.244.189.111   worker2   <none>           <none>
nginx-deployment-5c58774b6-9lnrj   1/1     Running   0          6s    10.244.235.138   worker1   <none>           <none>

動作確認のため、1つのPodを手動で削除します。一時的にPod数が1つに減少します。

[root@control ~]# kubectl delete pods nginx-deployment-5c58774b6-6j48r
pod "nginx-deployment-5c58774b6-6j48r" deleted from default namespace

再度 Pod の状態を確認すると、新しい Pod が作成されていることがわかります。これは、Deployment が指定されたレプリカ数を維持するため、削除された Pod を自動的に再作成しているためです。

[root@control ~]# kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-5c58774b6-2xxst   1/1     Running   0          14s   10.244.189.109   worker2   <none>           <none>
nginx-deployment-5c58774b6-9lnrj   1/1     Running   0          46s   10.244.235.138   worker1   <none>           <none>

4.2 ローリングアップデート

動作中のPodで使用しているコンテナイメージのバージョンを確認すると1.23であることが確認できます。

[root@control ~]# kubectl describe pods nginx-deployment-5c58774b6-2xxst|grep "Image:"
    Image:          nginx:1.23

マニフェストを修正するので、現在のマニフェストをバックアップします。

[root@control ~]# cp nginx.yaml nginx.old.yaml

マニフェストに記載されているコンテナイメージのバージョンを、1.23から1.25に変更します。

[root@control ~]# vi nginx.yaml
[root@control ~]# diff -Nur nginx.old.yaml nginx.yaml
--- nginx.old.yaml      2026-04-09 13:31:04.924645563 +0900
+++ nginx.yaml  2026-04-09 13:31:30.488907377 +0900
@@ -19,6 +19,6 @@
     spec:
       containers:
       - name: nginx
-        image: nginx:1.23
+        image: nginx:1.25
         ports:
         - containerPort: 80

変更したマニフェストをクラスタに適用します。この操作により、Deploymentは既存のPodを段階的に置き換えるローリングアップデートを実行します。

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

Podの状態を確認します。

[root@control ~]# kubectl get pods -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-569f95f5cb-4qb8g   1/1     Running   0          7s    10.244.189.106   worker2   <none>           <none>
nginx-deployment-569f95f5cb-65lkz   1/1     Running   0          7s    10.244.235.139   worker1   <none>           <none>

Podのコンテナイメージを確認すると、バージョンが1.25に変更されていることが確認できます。

[root@control ~]# kubectl describe pods nginx-deployment-569f95f5cb-4qb8g|grep "Image:"
    Image:          nginx:1.25

Deploymentを確認しても、指定したコンテナイメージが1.25に更新されていることがわかります。

[root@control ~]# kubectl get deployments -o wide
NAME               READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS   IMAGES       SELECTOR
nginx-deployment   2/2     2            2           2m50s   nginx        nginx:1.25   app=nginx

4.3 ロールバック

Deployment の更新履歴を確認します。REVISION 1 は初期状態、REVISION 2 は更新後の状態を表しています。

[root@control ~]# kubectl rollout history deployment/nginx-deployment
deployment.apps/nginx-deployment
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

kubectl rollout undo を実行し、REVISION 1 を指定して Deployment を以前の状態に戻します。

[root@control ~]# kubectl rollout undo deployment/nginx-deployment --to-revision=1
deployment.apps/nginx-deployment rolled back

Pod のコンテナイメージを確認すると、nginx:1.23 に戻っていることが確認できます。

[root@control ~]# kubectl describe pods nginx-deployment-5c58774b6-g9trr | grep "Image:"
    Image:          nginx:1.23

4.4 スケーリング

Deploymentのレプリカ数を変更し、Podの数が増減することを確認します。ここでは、Podの数を2から3に変更します。

マニフェストを修正するので、現在のマニフェストをバックアップします。

[root@control ~]# cp nginx.yaml nginx.old.yaml
cp: 'nginx.old.yaml' を上書きしますか? y

マニフェストの replicas を 2 から 3 に変更します。

[root@control ~]# vi nginx.yaml

変更内容を確認します。

[root@control ~]# diff -Nur nginx.old.yaml nginx.yaml
--- nginx.old.yaml      2026-04-09 14:07:44.312013444 +0900
+++ nginx.yaml  2026-04-09 14:08:18.137357762 +0900
@@ -3,7 +3,7 @@
 metadata:
   name: nginx-deployment
 spec:
-  replicas: 2
+  replicas: 3
   strategy:
     type: RollingUpdate
     rollingUpdate:

変更したマニフェストをクラスタに適用します。

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

Podの状態を確認します。Podの数が3になっていることが確認できます。

NAME                               READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-5c58774b6-drpfk   1/1     Running   0          9s    10.244.235.141   worker1   <none>           <none>
nginx-deployment-5c58774b6-g9trr   1/1     Running   0          87s   10.244.235.133   worker1   <none>           <none>
nginx-deployment-5c58774b6-zwmt7   1/1     Running   0          87s   10.244.189.114   worker2   <none>           <none>

Pod のラベルを確認します。出力結果から、すべての Pod にマニフェストで指定したラベル(app=nginx)が設定されていることがわかります。また、Deployment によって自動的に付与された pod-template-hash ラベルも設定されていることが確認できます。

[root@control ~]# kubectl get pods --show-labels
NAME                               READY   STATUS    RESTARTS   AGE     LABELS
nginx-deployment-5c58774b6-dsqtf   1/1     Running   0          23s     app=nginx,pod-template-hash=5c58774b6
nginx-deployment-5c58774b6-nkn22   1/1     Running   0          9m45s   app=nginx,pod-template-hash=5c58774b6
nginx-deployment-5c58774b6-vvk4w   1/1     Running   0          9m45s   app=nginx,pod-template-hash=5c58774b6

Pod のラベルを変更します。ここでは、nginx-deployment-5c58774b6-drpfk の app ラベルを nginx から changed に変更します。--overwrite オプションを指定することで、既存のラベル値を上書きできます。

[root@control ~]# kubectl label pods nginx-deployment-5c58774b6-drpfk app=changed --overwrite
pod/nginx-deployment-5c58774b6-drpfk labeled

Podのラベルを変更すると、Deploymentのセレクタ(app=nginx)に一致しなくなるため、Deploymentは管理対象のPodが不足したと判断し、新たにPodを1つ起動します。

[root@control ~]# kubectl get pods --show-labels
NAME                               READY   STATUS    RESTARTS   AGE     LABELS
nginx-deployment-5c58774b6-8b8gl   1/1     Running   0          20s     app=nginx,pod-template-hash=5c58774b6
nginx-deployment-5c58774b6-drpfk   1/1     Running   0          92s     app=changed,pod-template-hash=5c58774b6
nginx-deployment-5c58774b6-g9trr   1/1     Running   0          2m50s   app=nginx,pod-template-hash=5c58774b6
nginx-deployment-5c58774b6-zwmt7   1/1     Running   0          2m50s   app=nginx,pod-template-hash=5c58774b6

ラベルを変更したPodは、Deploymentの管理対象外となっているため、このPodを削除しても、Deploymentによる再作成は行われません。

[root@control ~]# kubectl delete pods nginx-deployment-5c58774b6-drpfk
pod "nginx-deployment-5c58774b6-drpfk" deleted from default namespace

Podの状態を確認します。削除したPodは再作成されず、Deploymentによって管理されているPodのみが存在していることが確認できます。また、Deploymentはレプリカ数(3個)を維持するように動作しているため、現在も3つのPodが稼働していることがわかります。

[root@control ~]# kubectl get pods -o wide
NAME                               READY   STATUS    RESTARTS   AGE     IP               NODE      NOMINATED NODE   READINESS GATES
nginx-deployment-5c58774b6-8b8gl   1/1     Running   0          61s     10.244.189.115   worker2   <none>           <none>
nginx-deployment-5c58774b6-g9trr   1/1     Running   0          3m31s   10.244.235.133   worker1   <none>           <none>
nginx-deployment-5c58774b6-zwmt7   1/1     Running   0          3m31s   10.244.189.114   worker2   <none>           <none>
[root@control ~]#

次の検証のため、クラスタに設定したマニフェストを削除します。

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

Podを確認します。マニフェストを削除したので、Podも削除されたことがわかります。

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

Z 参考図書

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

単行本

電子書籍