2024年10月29日
連載 第12回
Helm Starter Packsと
Helm Diffの利用
今回は社内で開発したアプリケーションをクバネティス環境でインストールするため、 Helmテンプレートを利用する方法を確認します。Helm Starter Packsを利用すると、便利なカスタマイズテンプレートを使用できるため、利便性とセキュリティーを向上できます。また、運用環境で安全にHelmのアップグレードができる Helm Diffコマンドを実習で確認します。
主な内容:
• 企業または個人の環境で事前に定義したHelmテンプレートを使用する Helm Starter Packsを使用して、効率的に Helmチャートを管理できます。
• Helm diffを使用するとアップグレード作業時に、変更内容を事前に確認できるため、障害予防に繋がります。
実習課題:
• Helm Starter Packsを利用したHelmテンプレートチャートの生成
• Helmのアップグレード作業(Helm diffコマンドを使用して Helmチャートアップグレード作業前の変更内容の事前確認)
今回の実習で使用するソースファイルのGitHubディレクトリは次の通りです。
• https://github.com/junghoon2/k8s-class/tree/main/helm-starter-template
1. デフォルト設定でHelmチャートの生成 – Helm Create
ユーザー定義 Helmチャートを生成するデフォルトコマンドは helm createです。
新しい Helmチャートを開始するとき、helm createコマンドは速く開始できる標準ディレクトリ構造と必要なファイルセットを提供します。このコマンドを実行すると、Helmは開発担当者が自身のアプリケーションの要求に合わせて変更ができるデフォルトテンプレートを生成します。テンプレートに必要な values.yamlファイルを変更して、ユーザーが必要なアプリケーションを作成できます。
簡単な実習で詳細内容を確認します。使用する Helmのバージョンで若干の差はありますが、バージョン3.12.3を使用します。
1. (latte-prod:kube-system)k8s-class$ helm version
2. version.BuildInfo{Version:"v3.12.3", GitCommit:"3a31588ad33fe3b89af5a2a54ee1d25bfe6eaa5e", GitTreeState:"clean", GoVersion:"go1.20.7"}
新しい Helmチャートを生成します。
1. $ (⎈ |switch-singapore-test:argocd) helm create foo-chart
2. Creating foo-chart
foo-chartという新しいディレクトリが生成され、下記のような構造でファイルが作成されます。
1. $ (⎈ |switch-singapore-test:argocd) ls foo-chart
2. Chart.yaml charts templates values.yaml
1. $ (⎈ |switch-singapore-test:argocd) tree -L 2 foo-chart
2. foo-chart
3. ├── Chart.yaml
4. ├── charts
5. ├── templates
6. │ ├── NOTES.txt
7. │ ├── _helpers.tpl
8. │ ├── deployment.yaml
9. │ ├── hpa.yaml
10. │ ├── ingress.yaml
11. │ ├── service.yaml
12. │ ├── serviceaccount.yaml
13. │ └── tests
14. └── values.yaml
15.
16. 4 directories, 9 files
デフォルトで含まれているテンプレートディレクトリ、values.yaml、Chart.yamlを変更して必要なチャートを作成できます。しかし、運用環境で多く使用するConfigMap、Secret等のリソースファイルが含まれていないため、手動での追加作業が多くあります。
デフォルトテンプレートをベースに変更を完了すると、helm packageコマンドを使用してHelmチャートを圧縮ファイルで作成できます。
1. $ (⎈ |switch-singapore-test:argocd) helm package foo-chart
2. Successfully packaged chart and saved it to: /Users/jerry/private/k8s-class/foo-chart-0.1.0.tgz
3.
4. $ (⎈ |switch-singapore-test:argocd) ls foo-chart-0.1.0.tgz
5. foo-chart-0.1.0.tgz
この圧縮ファイルでHelmチャートを共有できます。
しかし、上記の方法でHelmチャートを生成すると毎回 values.yamlとテンプレートディレクトリを変更する必要があります。作業は人が行うため、ヒューマンエラーが発生する可能性があり、追加の時間も必要です。
カスタマイズチャートを使用できる Helm Starter Packsオプションを確認します。
2. Helm Starter Packsを利用したカスタマイズアプリケーションのインストール実習
デフォルトで提供するHelmチャートは ConfigMap、Secret、セキュリティーの設定等が不足しています。 Helm Starter Packsテンプレートはクバネティスアプリケーションを速く開発し、配布するために使用するテンプレートセットです。企業または個人がテンプレートを指定してそのテンプレートをベースに新しい Helmチャートを作成できるオプションです。
Starter Packsはプロジェクトを始めるときに必要なデフォルト構成と設定を含むため、開発担当者と運用担当者がアプリケーションを簡単に開始できて、管理もできるようにサポートします。組織毎、個人毎のアプリケーション開発と配布に必要な標準化された構造を提供し、チーム間の一貫性を維持してアプリケーションプロジェクトのメンテナンスを容易にします。
また、基本的なクバネティスリソースおよび Helm Chart構造が提供され、新しいプロジェクトを始めるときに複雑な設定作業が無く、アプリケーション開発に集中できます。高いセキュリティーとベストプラクティス基盤の上に作成され、セキュリティーの危険性を最少化し、運用の効率性を向上できます。
–starter(開始者)オプションを使用すると、文字通り開始するファイルを任意に指定してテンプレートファイルをベースにHelmチャートを作成できます。テンプレートファイルに事前に必要なセキュリティー構成、ConfigMap等を含めると、会社のセキュリティーポリシーなどを遵守するHelmチャートを作成できます。
筆者が使用する Starter Packsテンプレートディレクトリは次の通りです。 Githubで全体ファイルをダウンロードできます。
helm-starter-template
1. $ (⎈ |SWITCH-SEOUL-PROD:nlp) tree -L 2 helm-starter-template
2. helm-starter-template
3. ├── Chart.yaml
4. ├── ci
5. │ └── values.yaml
6. └── templates
7. ├── NOTES.txt
8. ├── _helpers.tpl
9. ├── deployment.yaml
10. ├── env-cm.yaml
11. ├── hpa.yaml
12. ├── ingress.yaml
13. ├── service.yaml
14. ├── serviceaccount.yaml
15. ├── servicemonitor.yaml
16. └── tests
17.
18. 4 directories, 11 files
. ci/values.yaml
デフォルトの values.yamlファイルをciディレクトリに移動しました。環境構成により values.yamlファイルをコピーして test-values.yaml、dev/stage/prod-values.yaml 等で、Helm valuesファイルを追加して使用できます。
. templatesファイルの変更
デフォルトテンプレートに含まれていないservicemonitor、ingress、env-cm(configMap)等のファイルを追加して、デフォルトの deployment.yamlファイルに terminationGracePeriodSeconds等のカスタマイズ設定を追加しました。
次に、詳細設定を確認します。変更した Helm バリューファイルです。
ci/values.yaml
1. image:
2. repository: pengbai/docker-supermario
3. pullPolicy: Always
4. tag: "latest" #イメージタグを指定します。
5.
6. env: {}
7. #上の{}を削除して<環境変数>:<値>を記入してください。
8. # e.g.
9. # SERVER_PORT: 8080
10. # SERVER_HOST: atlas-default
11. # ...
12.
13. secrets: {}
14. # 上の{}を削除して<環境変数>: <値>を記入してください。
15. # e.g.
16. # PostgreSQL_PASSWORD: cGFzc3dvcmQ=
17. # ...
18.
19. terminationGracePeriodSeconds: 30
20.
21. metrics:
22. enabled: false # メトリックの使用可否を決定します。
23. path: /metrics # メトリックパスを指定します。
24.
25. probes:
26. liveness:
27. enabled: false
28. path: / # liveness パスを変更することができます。 e.g. /healthz
29. readiness:
30. enabled: false
31. path: / # readiness パスを変更することができます。 e.g. /healthz
32.
33. imagePullSecrets: []
34. nameOverride: ""
35. fullnameOverride: ""
36.
37. # Authentication
38. serviceAccount:
39. create: false
40. annotations: {}
41. name: ""
42.
43. podAnnotations: {}
44.
45. # Security
46. podSecurityContext: {}
47. securityContext:
48. allowPrivilegeEscalation: false
49. privileged: false
50. capabilities:
51. drop:
52. - ALL
53. readOnlyRootFilesystem: true
54.
55. # Networking
56. container:
57. port: 8080
58.
59. service:
60. type: ClusterIP
61. port: 80
62.
63. ingress:
64. enabled: false
65. className: ""
66. annotations: {}
67. hosts:
68. - host: chart-example.local
69. paths:
70. - path: /
71. pathType: ImplementationSpecific
72. tls: []
73.
74. # Container resources
75. resources:
76. limits:
77. # memory: 128Mi
78. requests:
79. cpu: 10m
80. memory: 128Mi
81.
82. # HPA
83. autoscaling:
84. enabled: false
85. minReplicas: 1
86. maxReplicas: 10
87. targetCPUUtilizationPercentage: 80
88.
89. # Scheduling
90. nodeSelector: {}
91. tolerations: []
92. affinity: {}
. image.repository
社内(Harbor)または外部イメージの保存場所(ECR)のアドレスを指定します。
. image.pullPolicy: Always
セキュリティー上、デフォルトの IfNotPresent よりは Always の使用を推奨します。毎回イメージをダウンロードしてネットワークトラフィックが増加し、Podが実行する速度は遅くなる可能性はありますが、悪質ユーザーがローカルにダウンロードしたイメージを変更して、有害なコードを追加する攻撃などを予防できます。 Prod環境では Alwaysを、Dev/Stage環境では IfNotPresent を使用することが一般的です。
latestバージョン(最新バージョン)で IfNotPresentオプションを使用すると、イメージ変更が反映できない問題が発生する可能性があるため、コンテナーイメージバージョンは latestを使用しないことを推奨します。
. env、secrets
コンテナーイメージ内の構成情報をハードコーディングできないように、必須で使用する ConfigMapとSecretで別途分離しました。環境変数で分離して Dev/Stage/Prodなど環境により変わらない不変イメージを使用することを推奨します。
勿論、Secretファイルは敏感な情報が含まれているため、base64インコーディングの内容をそのまま使用することはお勧めしません。運用環境では Vault、Sealed Secret、SOPSなどを使用することを推奨します。
. terminationGracePeriodSeconds
サービスの安定性のために Grace Shutdown設定をしました。使用するアプリケーションによるサービスが異常終了せず、安定的に終了できるのに必要な時間を指定します。筆者は 14,400(4時間)を指定します。
. metrics
アプリケーション毎にCustomメトリックスを使用する場合、 ServiceMonitor CRDに登録します。ServiceMonitorは Prometheus Operatorでカスタマイズメトリックスを収集して、クバネティス環境でサービスをモニタリングするときに使用します。 ServiceMonitorリソースは Prometheus Operatorが管理するPrometheusインスタンスにより自動的に発見され、メトリック収集のためのターゲットサービスのリストを動的にアップデートします。
. probes
コンテナーが正常に動作しているかを確認するときに使用する Liveness Probe、およびコンテナーがリクエストを処理する準備ができているかを確認するときに使用する Readiness Probe設定を追加しました。
. securityContext
企業のコンテナーセキュリティーポリシーを追加しました。セキュリティーは最初からポリシーで提供しないと、以降に修正することは非常に難しいです。Pod実行時、強制的にHelmチャートにデフォルトで含まれています。
. securityContext.allowPrivilegeEscalation
allowPrivilegeEscalationオプションが true に設定されると、コンテナープロセスがより多くの権限を得ることができる条件になります。これは主にコンテナーで実行されるプロセスが set-user-IDまたはset-group-ID権限を持つ実行ファイルを使用するときに発生します。この実行ファイルを使用すると、プロセスはファイルを所有したユーザー、グループの権限で実行できます。
falseオプションを指定すると権限の強化、即ちroot実行を防止してより安全に使用できます。セキュリティーの推奨事項です。
. securityContext.privileged
クバネティスの securityContext設定で privilegedオプションはコンテナーがホストシステムの全ての装置に対してアクセスできるようにする設定です。このオプションが trueに設定されると、コンテナー内のプロセスはホストシステムでほぼ全ての作業を実行できる権限を持つことになります。
Privilegedモードで実行されるコンテナーは、ホストマシンのカーネル機能に対してハイレベルのアクセス権限を持つことになります。これは一般コンテナーより多くの権限を与えられます。例えば、ホストのネットワークスタックを操作することや、他のコンテナーに影響を与える作業ができ危険です。
従って、明示的な false設定を推奨します。
. securityContext.capabilities
capabilitiesは Linux機能(capabilities)のコンテナープロセスへの追加と削除を管理します。 Linux機能は一般的にroot権限をより細分化して、特定権限をプロセスに与えることができるシステムです。追加権限が必要であれば、必要な機能のみを明示的に指定して実行します。
. container.port: 8080
コンテナーが実行するポートです。システムユーザーが使用する1024以上のポートを使用することを推奨します。
. service.port: 80
外部 Podまたはユーザーが接続するサービスポートです。コンテナー内部のポートではなく、外部で接続するポートのため、一般Webで使用することと同様に 80ポートを使用すると便利です。
. ingress
Podが外部アクセスの必要があれば、ingressを利用できるようにingress設定を追加します。 Annotations、Host情報等を追加すると、Helmチャート配布時にingressリソースと同時に配布されます。External DNS Controllerを一緒に使用する Host情報をベースに自動的にRoute 53のドメインまで登録するため、追加作業が不要で便利です。
. resources
デフォルトの設定でミスし易い設定にリソースの RequestsとLimitsの設定があります。参考までに筆者はリソースを効果的に使用するためにCPUは Limitの設定をしません。
. autoscaling
Helmチャートに HPAリソースも同時に配布できるようにautoscaling設定が含まれています。必要により trueかfalseオプションを指定します。
. tolerations、affinity
Advanced Scheduling関連の設定を追加しました。 taint設定されたノードにスケジューリング可能なtoleration設定を追加します。 podAffinity、nodeAffinityの設定で必要な Podとノードで実行します。
では、該当 Starter Packsチャートをベースに新しいチャートを作成します。helm createオプションに–starterを追加して、上記で生成したStarter Packsディレクトリの場所を指定します。
1. $ (⎈ |SWITCH-SEOUL-PROD:nlp) helm create foo --starter ~/private/k8s-class/helm-starter-template
2. Creating foo
新しい foo Helmチャートが生成されました(筆者 Githubファイル)。チャートに必要な設定が含まれた新しい Helmアプリケーションを配布します。
既存の values.yamlファイルに ConfigMapと Secret部分を下記のように変更します。 secretsに登録された変数は base64でインコーティングされた値で登録します。
ci/values.yaml
1. env:
2. SERVER_PORT: 8080
3.
4. secrets:
5. PostgreSQL_PASSWORD: cGFzc3dvcmQ=
Helmチャートをインストールします。
1. (jerry-test:default)foo$ cd foo
2. (jerry-test:default)foo$ k ns default
3. (jerry-test:default)foo$ helm install foo -f ci/values.yaml .
4. NAME: foo
5. LAST DEPLOYED: Wed Nov 8 05:34:21 2023
6. NAMESPACE: default
7. STATUS: deployed
8. REVISION: 1
9. NOTES:
10. 1. Get the application URL by running these commands:
11. export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=foo,app.kubernetes.io/instance=foo" -o jsonpath="{.items[0].metadata.name}")
12. export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
13. echo "Visit http://127.0.0.1:8080 to use your application"
14. kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
Helm Starterチャートに含まれた設定がConfigMap、Secretリソースで正常にインストールされます。
1. (jerry-test:default)foo$ k get pod,cm,secret
2. NAME READY STATUS RESTARTS AGE
3. pod/foo-655c44d96b-gtzqb 1/1 Running 0 97s
4.
5. NAME DATA AGE
6. configmap/foo-env 1 98s
7.
8. NAME TYPE DATA AGE
9. secret/foo-secret Opaque 1 98s
実行中のPodに接続して環境変数を確認すると、ConfigMap、Secretに登録した内容が確認できます。 base64でインコーティングされた文字列が平文に変換されて正常に出力されます。
1. $ (⎈ |switch-singapore-test:default) k exec -it foo-starter-6f9d56858c-rtkxm -- sh
2. $ echo $SERVER_PORT
3. 8080
4. $ echo $PostgreSQL_PASSWORD
5. password
port-forwardingコマンドを利用して簡単に Webページに接続できます。
1. (jerry-test:default)foo$ k get svc foo
2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
3. foo ClusterIP 172.20.107.67 <none> 80/TCP 3m7s
4.
5. (jerry-test:default)foo$ k port-forward svc/foo 8080:80
6. Forwarding from 127.0.0.1:8080 -> 8080
7. Forwarding from [::1]:8080 -> 8080
サービス名でport-forwardingコマンドを実行します。筆者がデフォルトイメージに指定したスーパーマリオの正常な実行を確認できます。
3. Helm Diffコマンドを利用した安全な Helmのアップグレード
クバネティスを運用するとHelmチャートでインストールしたアプリケーションを変更する場合があります。Helmチャートをアップグレードするときは予想外の変更による問題を防ぐために注意が必要です。そこでHelm Diffプラグインの役割が重要です。Helm Diffコマンドを使用して Helmチャートを安全にアップグレードする方法を確認します。
全ての作業に共通しますが、作業をする前は作業の変更内容を正確に把握することが必要です。そこで、Helm Diffが役に立ちます。同じくargocd diff、terraform planなども必要です。 CI Processに変更内容をチェックする部分を自動化してヒューマンエラーを防ぐのも良い方法です。
筆者もHelm Diffを使用して障害を減らすことができました。
Helm Diffプラグインは配布されたリソースと変更するチャートのバージョン間の差を視覚的に比較できるようにするツールです。これを使用する理由は次の通りです。
• 変更内容の把握:アップグレードの前に変更されるリソースの正確な内容を確認できます。
• 問題の予防:ミスで発生する構成エラーを事前に検知して修正できます。
• 検討と承認のプロセス:変更内容についてチームメンバーの検討と承認のプロセスを行うことができます。
Helm DiffはHelmに付加的に使用できるプラグインで下記のようにインストールします。
1. helm plugin install https://github.com/databus23/helm-diff
次は実習です。実際の業務で Resource Request、Limitを変更する作業は多く発生します。このような作業をベースに説明します。
前に設定したfoo Helmチャートの Memory Limitを Requestと同じく 128Miに設定します。
変更前:
1. # Container resources
2. resources:
3. limits:
4. # memory: 128Mi
5. requests:
6. cpu: 10m
7. memory: 128Mi
変更後:
1. # Container resources
2. resources:
3. limits:
4. memory: 128Mi
5. requests:
6. cpu: 10m
7. memory: 128Mi
運用環境ではメモリーの RequestとLimitを同じ値に設定することを推奨します。 Limitを設定しない場合や、Requestより高く設定した場合、Podの使用量が増加すると、同じノードにある他のPodに影響を与えることがあるため、OOM(Out Of Memory)障害が発生する可能性があります。
では helm diff コマンドで変更内容を確認します。
1. (jerry-test:default)foo$ helm diff upgrade foo -f ci/values.yaml .
2. default, foo, Deployment (apps) has changed:
3. # Source: foo/templates/deployment.yaml
4. (...)
5. - limits: null
6. + limits:
7. + memory: 128Mi
計画通りにlimits設定だけが変更されました。 limits: null設定が除外されてlimits: memory: 128Mi設定が追加されています。もし、limits設定の変更以外に他の変更がある場合は、現在のコードと実際にクバネティスの実行状態が異なることを確認できます。 k editなどでソースを変更しないことが一般的です。
現場では他のユーザーが Githubソースコードを Commitなく任意に変更した場合には、 helm diff で確認できます。 helm diffコマンドで確認しないで、アップグレード作業を進めると、変更された部分が以前の設定に戻って、障害が発生する可能性があります。
変更の有無を確認してアップグレード作業を進めます。
1. (jerry-test:default)foo$ helm upgrade foo -f ci/values.yaml .
2. Release "foo" has been upgraded. Happy Helming!
3. NAME: foo
4. LAST DEPLOYED: Thu Nov 9 09:14:49 2023
5. NAMESPACE: default
6. STATUS: deployed
7. REVISION: 2
8. NOTES:
9. 1. Get the application URL by running these commands:
10. export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=foo,app.kubernetes.io/instance=foo" -o jsonpath="{.items[0].metadata.name}")
11. export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
12. echo "Visit http://127.0.0.1:8080 to use your application"
13. kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
新しく生成したPodの設定を grep -A(after) 5コマンドで確認すると、必要な設定が正常に反映されていました。
1. (jerry-test:default)foo$ k get pod foo-7d46f9cb5b-t67kd -oyaml |grep -i -A 5 resources
2. resources:
3. limits:
4. memory: 128Mi
5. requests:
6. cpu: 10m
7. memory: 128Mi
上記のように Helmのアップグレード作業時には、必ずdiffコマンドで確認する習慣をつけると事前に障害を予防できます。
以上で今回の実習を完了しましたので、関連リソースを削除します。
1. (jerry-test:default)foo$ helm ls
2. helmNAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
3. foo default 2 2023-11-09 09:14:49.182849 +0900 KST deployed foo-0.1.0 0.1.0
4.
5. (jerry-test:default)foo$ helm delete foo
6. release "foo" uninstalled
Helmリストを確認してから(ls)、リソースを削除します。
今回は Helm Starter Packsと Helm Diffについて確認しました。
1. Helm Starter Packs公式ホームページ : https://helm.sh/docs/topics/charts/#chart-starter-packs
2. Helm diff公式ホームページ : https://github.com/databus23/helm-diff