Helm Chartの実践的活用法
コンテナアプリケーションで人気のツール「Kubernetes」は、ギリシャ語の「パイロット」または「船を操縦する人」という意味にちなんで命名されています。どんな旅でも操縦士は地図と同じくらい重要なものです。
アプリケーションのHelm chartとは操縦士の地図、つまりK8sリソースの関連セットを記述するHelm repositoryからデプロイできるファイルコレクションです。最適な方法でHelm chartを作成することができれば、Kubernatesが稼働環境でコンテナをデプロイする際に深みに嵌ることなく運用することができるでしょう。
しかし製品のデプロイ用に公開しているK8s chartを開発している時に私が見つけたように、他の方法で運用する方法もあります。プルリクエスト毎にHelmコミュニティからフィードバックをもらうことにより、コンテナの運用と更新の両方で最良の結果を得るHelm chartの実践的な活用方法を知ることができます。
コミュニティやユーザによって使用される本番環境のK8s chartを書く時に気をつけなければならないポイントを紹介します。特に気に留めておくべきことは以下のポイントです:
- どの依存関係を定義する必要があるか?
- アプリケーションは永続的な稼働が必要か?
- 機密事項と許可を通してセキュリティーをどう取り扱っていくか?
- 実行中のKubeletコンテナをどうやってコントロールするか?
- アプリケーションが稼働中でリクエストを受信できるということをどうやって確認するか?
- アプリケーションのサービスをどのように正式公開するか?
- Chartをどのようにテストするか?
これらに注意することで、実際にK8sがコンテナアプリケーションをdockにスムーズに展開してHelm chartを構成および作成するときに役立ちます。
最初のステップ
始める前にHelm chartを開発する必須手順についておさらいしましょう。
このガイドではExpress.jsを使ってMongoデータベースに2層コミットの作成、読み取り、更新、削除(CRUD)アプリケーションのデプロイを推奨する実践方法に基づいてHelm chartを作成します。
GitHubのexpress-crudにサンプルアプリケーションのソースコードを公開していますので参照ください。
Helm chartの作成
Helmクライアントのcreateコマンドを使ってHelm chartのテンプレートを作成してみましょう。
$ helm create express-crud
これでexpress-crud Helm chartのディレクトリ構造が作成されます。
開始するには、作成したChart.yamlファイルのchartメタデータを更新します。appVersion(dockerイメージタグとして使用されるアプリケーションのバージョン)、説明、バージョン(SemVer 2 バージョン文字列)、ソース、メンテナーとアイコンに適切な情報を追加してください。
apiVersion: v1
appVersion: "1.0.0"
description: A Helm chart for express-crud application
name: express-crud
version: 0.1.0
sources:
- https://github.com/jainishshah17/express-mongo-crud
maintainers:
- name: myaccount
email: myacount@mycompany.com
icon: https://github.com/mycompany17/mycompany.com/blob/master/app/public/images/logo.jpg
home: https://mycompany.com/
依存関係の定義
アプリケーションに依存関係がある場合はそれらを明記するHelm chartのディレクトリにrequirements.yamlファイルを作成しなければなりません。アプリケーションはmongodbデータベースが必要なので作成するrequirements.yamlファイルの依存関係リストに明記する必要があります。
例えば以下のようにrequirements.yamlファイルに記述します:
dependencies:
- name: mongodb
version: 3.0.4
repository: https://kubernetes-charts.storage.googleapis.com/
condition: mongodb.enabled
requirements.yamlファイル作成後はHelm chartでdependency updateコマンドを実行してください:
$ helm dep update
デプロイメントファイルの作成
Helm chartのデプロイメントファイルは\templatesサブディレクトリにあり、K8sがコンテナアプリケーションをデプロイする方法を指定しています。
また、デプロイメントファイルを開発する際に決めておくべきいくつかの重要事項があります。
DeploymentオブジェクトかStatefulSetオブジェクトか
作成するdeploymentファイルはアプリケーションがK8sを使用してDeploymentオブジェクトまたはStatefulSetオブジェクトとして管理するかどうかで違ってきます。
Deploymentオブジェクトはdeployment.yamlファイルで宣言され、kindパラメータをdeploymentとして指定するステートレスアプリケーションです。
Statefulオブジェクトは分散システムにおいて使用されるステートフルなアプリケーション用オブジェクトです。stateless.yamlファイルで宣言され、kindパラメータをstatefulとして指定します。
Deployment | StatefulSet |
Deploymentはステートレスな使用を目的としており比較的軽量です。 | StatefulSetは状態を永続化する必要がある場合に使われます。従って永続的なボリュームでvolumeClaimTemplatesを使用し、コンポーネントの再起動後も状態を維持できるようにします。 |
アプリケーションがステートレスである場合や起動中にバックエンドシステムから構築できる場合はDeploymentを使います。 | アプリケーションがステートフルである場合やKubernetes上にステートフルストレージをデプロイする場合はStatefulSetを使います。 |
このアプリケーションでは状態を永続化する必要がないためdeploymentオブジェクトを使用しています。
deployment.yamlファイルはhelm createコマンドで既に作成されています。
アプリケーションのDockerイメージタグとしてAppVersionを使用します。そうすることでChart.yamlで値を変更するだけでアプリケーションの新しいバージョンでHelm chartをアップグレードできます。
image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}"
SecretかConfigMapか
どの資格情報や設定データをSecretとして保存して、どれをConfigMapに格納できるかを決める必要があります。
SecretとはK8sが暗号化形式で保存するパスワードなどの機密情報です。
ConfigMapはアプリケーションによって共有される可能性のある構成情報を含むファイルです。ConfigMapの情報は暗号化されていないため機密情報を含めないでください。
Secret | ConfigMap |
情報をsecretに入れることはpod定義やdockerイメージに逐一格納していくよりも安全で柔軟です。 | ConfigMapを使用するとアーティファクトの設定をイメージコンテンツから分離し、コンテナ化されたアプリケーションの移植性を維持できます。 |
機密データに使用 | 非機密データに使用 |
使用例:APIキー、パスワード、トークン、sshキー | 使用例:ログローテータ、機密データなしの設定 |
この例ではHelmがイメージプルsecretを使用してプライベートdockerレジストリからdockerイメージをプルできるようにします。
この手順ではリポジトリマネジャのログイン資格情報を規定するKubernatesクラスタで利用可能なsecretを持つことに依存しています。このsecretはkubectlコマンドで以下のように作成できます:
Helm chartのvalues.yamlファイルでsecret名に値を渡すことができます:
$ kubectl create secret docker-registry regsecret --docker-server=$DOCKER_REGISTRY_RUL --docker-username=$USERNAME --docker-password=$PASSWORD --docker-email=$EMAIL
Helm chartのvalues.yamlファイルでsecret名に値を渡すことができます:
imagePullSecrets: regsecret
次にこのsecretを利用してHelmがdeployment.yamlの次の行を介してdockerレジストリにアクセスできるようにします:
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
- name: {{ .Values.imagePullSecrets }}
{{- end }}
アプリケーションで使用可能なsecretについては、その情報を直接values.yamlに追加する必要があります。
例えば事前に作成したユーザとデータベースでmongodbにアクセスするようにアプリケーションを構成するにはvalues.yamlにその情報を追加します。
mongodb:
enabled: true
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
Helm chartにデフォルトの資格情報をハードコーディングしないようにご注意ください。–set flagもしくはvalues.yamlでパスワードが提供されない場合にはlogicを使用しランダムのパスワードを生成します。
deployment.yamlの以下の行で、secretを使用してmongodbの資格情報をアプリケーションに渡します。
env:
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ .Release.Name }}-mongodb
key: mongodb-password
特別なInit ContainersやContainer Lifecycle Hooksを通してkubeletコンテナ実行をコントロールできます。
InitContainers | Container Lifecycle Hooks |
InitContainersはアプリコンテナの前に実行される特殊なコンテナで、アプリイメージに存在しないユーティリティやセットアップスクリプトを含めることができます。 | コンテナはContainer Lifecycle Hooksを使い管理サイクル中にイベントによってトリガーされるコードを実行します。 |
Podには1つ以上のInit Containersを含めることができ、アプリコンテナが開始される前に実行されます。
|
PodはPostStartまたはPreStopフックを1つだけ持つことができます。 |
PostStartフックはコンテナが作成された直後に実行されますが、コンテナのENTRYPOINTの前に実行されるという保証はありません。パラメータはハンドラに渡されません。例:ConfigMap/Secretsを使用してマウントされたファイルを別の場所に移動します。 | |
PreStopフックはコンテナが終了する直前に呼び出されます。これはブロッキング、つまり非同期であり、コンテナを削除する呼び出しを送信する前に完了する必要があります。
例: アプリケーションを正常にシャットダウンします。 |
|
initContainers を使って待ち時間を追加し、依存するマイクロサービスが機能することを確認してから次に進むことができます。 | Service IPで構成ファイルを更新するといったようにPostStartフックを使用して同じpodでファイルを更新します。 |
この例ではinitContainers仕様をdeployments.yamlに追加し、データベースが稼働するまでアプリケーションの起動をホールドします。
initContainers:
- name: wait-for-db
image: "{{ .Values.initContainerImage }}"
command:
- 'sh'
- '-c'
- >
until nc -z -w 2 {{ .Release.Name }}-mongodb 27017 && echo mongodb ok;
do sleep 2;
done
Readiness ProbeとLiveness Probeの追加
多くの場合においてアプリケーションの継続的な健全性を確認するためにReadiness probeやLiveness probeを追加して確認することは適切な対処です。そうしないとアプリケーションは実行されているように見えても、呼び出しやクエリに対して反応せず失敗している場合があります。
以下のように定期的な確認をするためにprobeをdeployment.yamlファイルに追加します:
livenessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
readinessProbe:
httpGet:
path: '/health'
port: http
initialDelaySeconds: 60
periodSeconds: 10
failureThreshold: 10
RBACサポートの追加
アプリケーションで必要な場合にはロールベースのアクセス制御 (RBAC) サポートがchartに追加されます。
ステップ1:次のコンテンツをrole.yamlファイルに追加することでロールを作成します:
ロールは単一のネームスペース内のリソースへのアクセスを許可するためにのみ使用できます。
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
rules:
{{ toYaml .Values.rbac.role.rules }}
{{- end }}
ステップ2:次のコンテンツをrolebinding.yamlファイルに追加することでRoleBindingを作成します:
ClusterRoleはロールとして同じ権限を付与できますが、cluster-scopedであるため次のアクセス権を付与するためにも使用できます:
- cluster-scopedリソース(ノードなど)
- non-resourceエンドポイント(“/healthz”など)
- すべてのネームスペースにおけるnamespacedリソース(podsなど)
{{- if .Values.rbac.create }}
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ template "express-crud.serviceAccountName" . }}
roleRef:
kind: Role
apiGroup: rbac.authorization.k8s.io
name: {{ template "express-crud.fullname" . }}
{{- end }}
ステップ3:次のコンテンツをserviceaccount.yamlファイルに追加することでServiceAccountを作成します:
サービスアカウントはPodで実行されるプロセスIDを提供します。
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
heritage: {{ .Release.Service }}
release: {{ .Release.Name }}
name: {{ template "express-crud.serviceAccountName" . }}
{{- end }}
ステップ4:helperテンプレートを使用してServiceAccount名を設定します。
_helpers.tplに次のコンテンツを追加することで設定します。
{{/*
Create the name of the service account to use
*/}}
{{- define "express-crud.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{ default (include "express-crud.fullname" .) .Values.serviceAccount.name }}
{{- else -}}
{{ default "default" .Values.serviceAccount.name }}
{{- end -}}
{{- end -}}
サービスの追加
次にサービスを介してアプリケーションを一般公開します。
サービスはIPアドレスを通してアプリケーション側でトラフィックを受信します。type指定することでサービスをさまざまな方法で公開できます:
ClusterIP | サービスはクラスタ内から内部IPのみで確認できます。 |
NodePort | サービスはNodeIPとNodePortを介してクラスタの外部からアクセスできます。 |
LoadBalancer | LoadBalancerサービスは外部ロードバランサを介してクラスタの外部からアクセスできます。アプリケーションにIngressを適用できます。 |
次のコンテンツをservice.yamlに追加することで実現します:
apiVersion: v1
kind: Service
metadata:
name: {{ template "express-crud.fullname" . }}
labels:
app: {{ template "express-crud.name" . }}
chart: {{ template "express-crud.chart" . }}
release: {{ .Release.Name }}
heritage: {{ .Release.Service }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.externalPort }}
targetPort: http
protocol: TCP
name: http
selector:
app: {{ template "express-crud.name" . }}
release: {{ .Release.Name }}
上記ではサービスのtypeについてvalues.yamlの設定を参照しています:
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
Values.yamlの概要
values.yamlでいろいろな設定を定義し試してみることはHelm chartをメンテナンスする上でも良いことです。
この例ではvalues.yamlファイルがどのように表示されるかを示しており、上記で説明した多くの機能に対して定義したさまざまな設定を表示しています:
# Default values for express-mongo-crud.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
## Role Based Access Control
## Ref: https://kubernetes.io/docs/admin/authorization/rbac/
rbac:
create: true
role:
## Rules to create. It follows the role specification
rules:
- apiGroups:
- ''
resources:
- services
- endpoints
- pods
verbs:
- get
- watch
- list
## Service Account
## Ref: https://kubernetes.io/docs/admin/service-accounts-admin/
##
serviceAccount:
create: true
## The name of the ServiceAccount to use.
## If not set and create is true, a name is generated using the fullname template
name:
## Configuration values for the mongodb dependency
## ref: https://github.com/kubernetes/charts/blob/master/stable/mongodb/README.md
##
mongodb:
enabled: true
image:
tag: 3.6.3
pullPolicy: IfNotPresent
persistence:
size: 50Gi
# resources:
# requests:
# memory: "12Gi"
# cpu: "200m"
# limits:
# memory: "12Gi"
# cpu: "2"
## Make sure the --wiredTigerCacheSizeGB is no more than half the memory limit!
## This is critical to protect against OOMKill by Kubernetes!
mongodbExtraFlags:
- "--wiredTigerCacheSizeGB=1"
mongodbRootPassword:
mongodbUsername: admin
mongodbPassword:
mongodbDatabase: test
# livenessProbe:
# initialDelaySeconds: 60
# periodSeconds: 10
# readinessProbe:
# initialDelaySeconds: 30
# periodSeconds: 30
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
path: /
hosts:
- chart-example.local
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
initContainerImage: "alpine:3.6"
imagePullSecrets:
replicaCount: 1
image:
repository: jainishshah17/express-mongo-crud
# tag: 1.0.1
pullPolicy: IfNotPresent
service:
type: LoadBalancer
internalPort: 3000
externalPort: 80
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}
Helm Chartのテストとインストール
Helm lintコマンドを使用してHelm chartをテストすることは非常に重要です。
$ helm lint ./ ## Output ==> Linting ./ Lint OK 1 chart(s) linted, no failures
Helm installコマンドを使用してKubernetesのhelm chartを使ったアプリケーションをデプロイします。
$ helm install --name test1 ./ ## Output NAME: test1 LAST DEPLOYED: Sat Sep 15 09:36:23 2018 NAMESPACE: default STATUS: DEPLOYED RESOURCES: ==> v1beta1/Deployment NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE test1-mongodb 1 1 1 0 0s ==> v1beta2/Deployment test1-express-crud 1 1 1 0 0s ==> v1/Secret NAME TYPE DATA AGE test1-mongodb Opaque 2 0s ==> v1/PersistentVolumeClaim NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE test1-mongodb Pending standard 0s ==> v1/ServiceAccount NAME SECRETS AGE test1-express-crud 1 0s ==> v1/Service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE test1-mongodb ClusterIP 10.19.248.205 27017/TCP 0s test1-express-crud LoadBalancer 10.19.254.169 80:31994/TCP 0s ==> v1/Role NAME AGE test1-express-crud 0s ==> v1/RoleBinding NAME AGE test1-express-crud 0s ==> v1/Pod(related) NAME READY STATUS RESTARTS AGE test1-mongodb-67b6697449-tppk5 0/1 Pending 0 0s test1-express-crud-dfdbd55dc-rdk2c 0/1 Init:0/1 0 0s NOTES: 1. Get the application URL by running these commands: NOTE: It may take a few minutes for the LoadBalancer IP to be available. You can watch the status of by running 'kubectl get svc -w test1-express-crud' export SERVICE_IP=$(kubectl get svc --namespace default test1-express-crud -o jsonpath='{.status.loadBalancer.ingress[0].ip}') echo https://$SERVICE_IP:80
上記のhelm installコマンドを実行するとロードバランサにExternal_IPが生成されます。そのIPを使ってアプリケーションを実行できます。
これは実行時にアプリケーションがどのように表示されるかを表しています:
最後に
この例からもわかるようにHelmはchartの構成と開発方法に高い柔軟性がある、非常に汎用性の高いシステムです。Helmコミュニティを活用することでHelm chartを公開して使用するプロセスが容易になり、アプリケーションを更新する際のメンテナンスがはるかに簡単になります。
このサンプルプロジェクトで完成したHelm chartはGitHubのexpress-crudリポジトリからダウンロードできますので、サンプルで機能を確認してどのように動くか理解してみてください。
Kubernetesに製品をデプロイするためのサンプルプロジェクトをもっと見つけるにはHelm chartのサンプルリポジトリを参照ください。